package com.hibegin.http.server.handler; import com.hibegin.common.util.IOUtil; import com.hibegin.common.util.LoggerUtil; import com.hibegin.http.HttpMethod; import com.hibegin.http.server.ApplicationContext; import com.hibegin.http.server.SimpleWebServer; import com.hibegin.http.server.api.HttpRequestDeCoder; import com.hibegin.http.server.api.HttpResponse; import com.hibegin.http.server.config.RequestConfig; import com.hibegin.http.server.config.ResponseConfig; import com.hibegin.http.server.config.ServerConfig; import com.hibegin.http.server.execption.RequestBodyTooLargeException; import com.hibegin.http.server.execption.UnSupportMethodException; import com.hibegin.http.server.impl.HttpRequestDecoderImpl; import com.hibegin.http.server.impl.SimpleHttpResponse; import com.hibegin.http.server.util.FileCacheKit; import com.hibegin.http.server.util.FrameUtil; import com.hibegin.http.server.util.StatusCodeUtil; import java.io.*; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.*; import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; public class HttpDecodeThread extends Thread { private static final Logger LOGGER = LoggerUtil.getLogger(HttpDecodeThread.class); private final ApplicationContext applicationContext; private final Map<SocketChannel, LinkedBlockingDeque<RequestEvent>> socketChannelBlockingQueueConcurrentHashMap = new ConcurrentHashMap<>(); private final SimpleWebServer simpleWebServer; private final RequestConfig requestConfig; private final ResponseConfig responseConfig; private final ServerConfig serverConfig; private final Set<SocketChannel> workingChannel = new CopyOnWriteArraySet<>(); private final BlockingQueue<HttpRequestHandlerRunnable> httpRequestHandlerRunnableBlockingQueue = new LinkedBlockingQueue<>(); public HttpDecodeThread(ApplicationContext applicationContext, SimpleWebServer simpleWebServer, RequestConfig requestConfig, ResponseConfig responseConfig) { this.applicationContext = applicationContext; this.simpleWebServer = simpleWebServer; this.requestConfig = requestConfig; this.responseConfig = responseConfig; this.serverConfig = applicationContext.getServerConfig(); setName("http-decode-thread"); } @Override public void run() { while (true) { List<SocketChannel> needRemoveChannel = new CopyOnWriteArrayList<>(); for (final Map.Entry<SocketChannel, LinkedBlockingDeque<RequestEvent>> entry : socketChannelBlockingQueueConcurrentHashMap.entrySet()) { final SocketChannel channel = entry.getKey(); if (entry.getKey().socket().isClosed()) { needRemoveChannel.add(channel); } else { if (!workingChannel.contains(channel)) { final LinkedBlockingDeque<RequestEvent> blockingQueue = entry.getValue(); if (!blockingQueue.isEmpty()) { final RequestEvent requestEvent = blockingQueue.poll(); if (requestEvent != null) { workingChannel.add(channel); serverConfig.getDecodeExecutor().execute(new Runnable() { @Override public void run() { doParseHttpMessage(requestEvent, channel.socket(), blockingQueue); workingChannel.remove(channel); synchronized (HttpDecodeThread.this) { HttpDecodeThread.this.notify(); } } }); } } } } } synchronized (this) { try { for (SocketChannel socketChannel : needRemoveChannel) { LinkedBlockingDeque<RequestEvent> entry = socketChannelBlockingQueueConcurrentHashMap.get(socketChannel); if (entry != null) { while (!entry.isEmpty()) { entry.poll().getFile().delete(); } socketChannelBlockingQueueConcurrentHashMap.remove(socketChannel); } workingChannel.remove(socketChannel); } this.wait(); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, "", e); } } } } private void doParseHttpMessage(RequestEvent requestEvent, Socket socket, LinkedBlockingDeque<RequestEvent> blockingQueue) { SelectionKey key = requestEvent.getSelectionKey(); Map.Entry<HttpRequestDeCoder, HttpResponse> codecEntry = applicationContext.getHttpDeCoderMap().get(socket); File file = requestEvent.getFile(); try { if (codecEntry != null) { Map.Entry<Boolean, ByteBuffer> booleanEntry = codecEntry.getKey().doDecode(ByteBuffer.wrap(IOUtil.getByteByInputStream(new FileInputStream(file)))); if (booleanEntry.getKey()) { if (booleanEntry.getValue().limit() > 0) { blockingQueue.addFirst(new RequestEvent(key, FileCacheKit.generatorRequestTempFile(serverConfig.getPort(), booleanEntry.getValue().array()))); } if (serverConfig.isSupportHttp2()) { renderUpgradeHttp2Response(codecEntry.getValue()); } else { httpRequestHandlerRunnableBlockingQueue.add(new HttpRequestHandlerRunnable(codecEntry.getKey().getRequest(), codecEntry.getValue())); if (codecEntry.getKey().getRequest().getMethod() != HttpMethod.CONNECT) { HttpRequestDeCoder requestDeCoder = new HttpRequestDecoderImpl(requestConfig, applicationContext, codecEntry.getKey().getRequest().getHandler()); codecEntry = new AbstractMap.SimpleEntry<HttpRequestDeCoder, HttpResponse>(requestDeCoder, new SimpleHttpResponse(requestDeCoder.getRequest(), responseConfig)); applicationContext.getHttpDeCoderMap().put(socket, codecEntry); } } } } } catch (EOFException | ClosedChannelException e) { //do nothing handleException(key, codecEntry.getKey(), null, 400); } catch (UnSupportMethodException | IOException e) { LOGGER.log(Level.SEVERE, "", e); handleException(key, codecEntry.getKey(), new HttpRequestHandlerRunnable(codecEntry.getKey().getRequest(), codecEntry.getValue()), 400); } catch (RequestBodyTooLargeException e) { handleException(key, codecEntry.getKey(), new HttpRequestHandlerRunnable(codecEntry.getKey().getRequest(), codecEntry.getValue()), 413); } catch (Exception e) { LOGGER.log(Level.SEVERE, "", e); handleException(key, codecEntry.getKey(), new HttpRequestHandlerRunnable(codecEntry.getKey().getRequest(), codecEntry.getValue()), 500); } finally { file.delete(); } } public HttpRequestHandlerRunnable getHttpRequestHandlerThread() { try { return httpRequestHandlerRunnableBlockingQueue.take(); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, "", e); } return null; } public void doRead(SocketChannel channel, SelectionKey key) throws IOException { if (channel != null && channel.isOpen()) { Map.Entry<HttpRequestDeCoder, HttpResponse> codecEntry = applicationContext.getHttpDeCoderMap().get(channel.socket()); ReadWriteSelectorHandler handler; if (codecEntry == null) { handler = simpleWebServer.getReadWriteSelectorHandlerInstance(channel, key); HttpRequestDeCoder requestDeCoder = new HttpRequestDecoderImpl(requestConfig, applicationContext, handler); codecEntry = new AbstractMap.SimpleEntry<HttpRequestDeCoder, HttpResponse>(requestDeCoder, new SimpleHttpResponse(requestDeCoder.getRequest(), responseConfig)); applicationContext.getHttpDeCoderMap().put(channel.socket(), codecEntry); } else { handler = codecEntry.getKey().getRequest().getHandler(); } LinkedBlockingDeque<RequestEvent> entryBlockingQueue = socketChannelBlockingQueueConcurrentHashMap.get(channel); if (entryBlockingQueue == null) { entryBlockingQueue = new LinkedBlockingDeque<>(); socketChannelBlockingQueueConcurrentHashMap.put(channel, entryBlockingQueue); } entryBlockingQueue.add(new RequestEvent(key, FileCacheKit.generatorRequestTempFile(serverConfig.getPort(), handler.handleRead().array()))); synchronized (this) { this.notify(); } } } private void renderUpgradeHttp2Response(HttpResponse httpResponse) throws IOException { Map<String, String> upgradeHeaderMap = new LinkedHashMap<>(); upgradeHeaderMap.put("Connection", "upgrade"); upgradeHeaderMap.put("Upgrade", "h2c"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); bout.write(("HTTP/1.1 101 " + StatusCodeUtil.getStatusCodeDesc(101) + "\r\n").getBytes()); for (Map.Entry<String, String> entry : upgradeHeaderMap.entrySet()) { bout.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes()); } bout.write("\r\n".getBytes()); String body = "test"; bout.write(FrameUtil.wrapperData(body.getBytes())); httpResponse.send(bout, true); } private void handleException(SelectionKey key, HttpRequestDeCoder codec, HttpRequestHandlerRunnable httpRequestHandlerRunnable, int errorCode) { try { if (httpRequestHandlerRunnable != null && codec != null && codec.getRequest() != null) { if (!httpRequestHandlerRunnable.getRequest().getHandler().getChannel().socket().isClosed()) { httpRequestHandlerRunnable.getResponse().renderCode(errorCode); } } } catch (Exception e) { LOGGER.log(Level.SEVERE, "error", e); } finally { try { key.channel().close(); } catch (IOException e) { LOGGER.log(Level.SEVERE, "error", e); } key.cancel(); } } }