From 9cd7a0c7dd6f38fb1ad5de412e9588d9bebac4c9 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 3 Dec 2021 21:27:33 +0100 Subject: [PATCH 1/6] Started to implement a custom http server as an alternative to jetty --- .../javawebstack/httpserver/HTTPMethod.java | 15 ++ .../javawebstack/httpserver/HTTPStatus.java | 90 +++++++++ .../httpserver/socket/HTTPServerSocket.java | 29 +++ .../httpserver/socket/HTTPSocket.java | 183 ++++++++++++++++++ .../httpserver/socket/HTTPSocketWorker.java | 51 +++++ 5 files changed, 368 insertions(+) create mode 100644 src/main/java/org/javawebstack/httpserver/HTTPMethod.java create mode 100644 src/main/java/org/javawebstack/httpserver/HTTPStatus.java create mode 100644 src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java create mode 100644 src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java create mode 100644 src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java diff --git a/src/main/java/org/javawebstack/httpserver/HTTPMethod.java b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java new file mode 100644 index 0000000..e5fdad6 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java @@ -0,0 +1,15 @@ +package org.javawebstack.httpserver; + +public enum HTTPMethod { + + GET, + POST, + PUT, + PATCH, + DELETE, + HEAD, + OPTIONS, + TRACE, + CONNECT + +} diff --git a/src/main/java/org/javawebstack/httpserver/HTTPStatus.java b/src/main/java/org/javawebstack/httpserver/HTTPStatus.java new file mode 100644 index 0000000..e42c5c3 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/HTTPStatus.java @@ -0,0 +1,90 @@ +package org.javawebstack.httpserver; + +public enum HTTPStatus { + + OK(200, "OK"), + CREATED(201, "Created"), + ACCEPTED(202, "Accepted"), + NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), + NO_CONTENT(204, "No Content"), + RESET_CONTENT(205, "Reset Content"), + PARTIAL_CONTENT(206, "Partial Content"), + MULTI_STATUS(207, "Multi-Status"), + ALREADY_REPORTED(208, "Already Reported"), + IM_USED(226, "IM Used"), + + MULTIPLE_CHOICES(300, "Multiple Choices"), + MOVED_PERMANENTLY(301, "Moved Permanently"), + FOUND(302, "Found"), + SEE_OTHER(303, "See Other"), + NOT_MODIFIED(304, "Not Modified"), + USE_PROXY(305, "Use Proxy"), + TEMPORARY_REDIRECT(307, "Temporary Redirect"), + PERMANENT_REDIRECT(308, "Permanent Redirect"), + + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + PAYMENT_REQUIRED(402, "Payment Required"), + FORBIDDEN(403, "Forbidden"), + NOT_FOUND(404, "Not Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + NOT_ACCEPTABLE(406, "Not Acceptable"), + PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), + REQUEST_TIMEOUT(408, "Request Timeout"), + CONFLICT(409, "Conflict"), + GONE(410, "Gone"), + LENGTH_REQUIRED(411, "Length Required"), + PRECONDITION_FAILED(412, "Precondition Failed"), + REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), + REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), + UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), + REQUEST_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), + EXPECTATION_FAILED(417, "Expectation Failed"), + IM_A_TEAPOT(418, "I'm a teapot"), + UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"), + LOCKED(423, "Locked"), + FAILED_DEPENDENCY(424, "Failed Dependency"), + UPGRADE_REQUIRED(426, "Upgrade Required"), + PRECONDITION_REQUIRED(428, "Precondition Required"), + TOO_MANY_REQUESTS(429, "Too Many Requests"), + REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"), + UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"), + + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + NOT_IMPLEMENTED(501, "Not Implemented"), + BAD_GATEWAY(502, "Bad Gateway"), + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + GATEWAY_TIMEOUT(504, "Gateway Timeout"), + HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"), + VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), + INSUFFICIENT_STORAGE(507, "Insufficient Storage"), + LOOP_DETECTED(508, "Loop Detected"), + BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"), + NOT_EXTENDED(510, "Not Extended"), + NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required"); + + private final int status; + private final String message; + + HTTPStatus(int status, String message) { + this.status = status; + this.message = message; + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public static HTTPStatus byStatus(int status) { + for(HTTPStatus s : values()) { + if(s.status == status) + return s; + } + return null; + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java b/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java new file mode 100644 index 0000000..1d4f766 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java @@ -0,0 +1,29 @@ +package org.javawebstack.httpserver.socket; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +public class HTTPServerSocket { + + private final ServerSocket serverSocket; + + public HTTPServerSocket(int port) throws IOException { + serverSocket = new ServerSocket(port); + } + + public void close() throws IOException { + serverSocket.close(); + } + + public boolean isClosed() { + return serverSocket.isClosed(); + } + + public HTTPSocket accept() throws IOException { + Socket socket = serverSocket.accept(); + return new HTTPSocket(socket); + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java b/src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java new file mode 100644 index 0000000..52ceb51 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java @@ -0,0 +1,183 @@ +package org.javawebstack.httpserver.socket; + +import org.javawebstack.httpserver.HTTPMethod; +import org.javawebstack.httpserver.HTTPStatus; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class HTTPSocket { + + private final Socket socket; + private final InputStream inputStream; + private final OutputStream outputStream; + private final HTTPMethod requestMethod; + private final String requestPath; + private String requestQuery; + private final String requestVersion; + private final Map> requestHeaders = new HashMap<>(); + private final Map> responseHeaders = new LinkedHashMap<>(); + private int responseStatus = 200; + private String responseStatusMessage = "OK"; + private boolean headersSent; + + public HTTPSocket(Socket socket) throws IOException { + this.socket = socket; + this.inputStream = socket.getInputStream(); + this.outputStream = socket.getOutputStream(); + socket.getOutputStream().flush(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int lb = -1; + while (true) { + int b = inputStream.read(); + if(b == -1) { + socket.close(); + throw new IOException("Unexpected end of stream"); + } + if(b == '\r' && lb == '\n') { + b = inputStream.read(); + break; + } + baos.write(b); + lb = b; + } + String[] lines = new String(baos.toByteArray(), StandardCharsets.UTF_8).split("\\r?\\n"); + if(lines.length < 2) { + socket.close(); + throw new IOException("Invalid http request"); + } + String[] first = lines[0].split(" "); + if(first.length != 3 || !first[1].startsWith("/")) { + socket.close(); + throw new IOException("Invalid http request"); + } + requestMethod = HTTPMethod.valueOf(first[0]); + String[] pathSplit = first[1].split("\\?", 2); + requestPath = pathSplit[0]; + if(pathSplit.length == 2) + requestQuery = pathSplit[1]; + requestVersion = first[2]; + if(!requestVersion.equals("HTTP/1.1") && !requestVersion.equals("HTTP/1.0")) { + setResponseStatus(HTTPStatus.HTTP_VERSION_NOT_SUPPORTED); + writeHeaders(); + close(); + throw new IOException("Unsupported http version"); + } + for(int i=1; i values = requestHeaders.computeIfAbsent(hspl[0].toLowerCase(Locale.ROOT), h -> new ArrayList<>()); + values.add(hspl[1]); + } + } + + public HTTPSocket setResponseStatus(HTTPStatus status) { + return setResponseStatus(status.getStatus(), status.getMessage()); + } + + public HTTPSocket setResponseStatus(int status) { + HTTPStatus s = HTTPStatus.byStatus(status); + return setResponseStatus(status, s != null ? s.getMessage() : "Unknown"); + } + + public HTTPSocket setResponseStatus(int status, String message) { + this.responseStatus = status; + this.responseStatusMessage = message; + return this; + } + + public HTTPSocket setResponseHeader(String name, String value) { + responseHeaders.put(name.toLowerCase(Locale.ROOT), Arrays.asList(value)); + return this; + } + + public HTTPSocket addResponseHeader(String name, String value) { + responseHeaders.computeIfAbsent(name.toLowerCase(Locale.ROOT), h -> new ArrayList<>()).add(value); + return this; + } + + public void close() throws IOException { + socket.close(); + } + + public void writeHeaders() throws IOException { + headersSent = true; + StringBuilder sb = new StringBuilder(requestVersion) + .append(' ') + .append(responseStatus) + .append(' ') + .append(responseStatusMessage) + .append("\r\n"); + responseHeaders.forEach((k, l) -> l.forEach(v -> sb.append(k.toLowerCase(Locale.ROOT)).append(": ").append(v).append("\r\n"))); + sb.append("\r\n"); + outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + } + + public InputStream getInputStream() { + return inputStream; + } + + public OutputStream getOutputStream() { + return new HTTPOutputStream(); + } + + public HTTPMethod getRequestMethod() { + return requestMethod; + } + + public String getRequestPath() { + return requestPath; + } + + public String getRequestQuery() { + return requestQuery; + } + + public String getRequestVersion() { + return requestVersion; + } + + public Map> getRequestHeaders() { + return requestHeaders; + } + + public Map> getResponseHeaders() { + return responseHeaders; + } + + public int getResponseStatus() { + return responseStatus; + } + + public String getResponseStatusMessage() { + return responseStatusMessage; + } + + public boolean isClosed() { + return socket.isClosed(); + } + + private class HTTPOutputStream extends OutputStream { + public void write(int i) throws IOException { + if(!headersSent) + writeHeaders(); + outputStream.write(i); + } + public void close() throws IOException { + outputStream.close(); + } + public void flush() throws IOException { + outputStream.flush(); + } + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java b/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java new file mode 100644 index 0000000..11ded81 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java @@ -0,0 +1,51 @@ +package org.javawebstack.httpserver.socket; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +public class HTTPSocketWorker { + + private final Thread schedulerThread; + private ExecutorService executorService; + private final HTTPServerSocket serverSocket; + + public HTTPSocketWorker(HTTPServerSocket serverSocket, Consumer handler) { + this.serverSocket = serverSocket; + this.schedulerThread = new Thread(() -> { + while (!serverSocket.isClosed()) { + try { + HTTPSocket socket = serverSocket.accept(); + executorService.execute(() -> { + try { + handler.accept(socket); + if(!socket.isClosed()) + socket.close(); + } catch (IOException ex) {} + }); + } catch (IOException exception) {} + } + }); + } + + public HTTPSocketWorker start() { + this.executorService = Executors.newCachedThreadPool(); + this.schedulerThread.start(); + return this; + } + + public void join() { + try { + schedulerThread.join(); + } catch (InterruptedException e) {} + } + + public void stop() { + this.executorService.shutdown(); + try { + this.serverSocket.close(); + } catch (IOException e) {} + } + +} From fb70dd79a2c7f182d4843b7f6d22c9908353ce65 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Sun, 5 Dec 2021 09:35:18 +0100 Subject: [PATCH 2/6] Abstracted the http server logic to allow for using a custom http server implementation and finished the custom http implementation. Added a JettyHTTPSocketServer as a backup option, but it doesn't support websockets. --- .../org/javawebstack/httpserver/Exchange.java | 105 +++--- .../javawebstack/httpserver/HTTPMethod.java | 3 +- .../javawebstack/httpserver/HTTPServer.java | 109 +++--- .../javawebstack/httpserver/HTTPStatus.java | 4 + .../httpserver/adapter/IHTTPSocket.java | 58 ++++ .../adapter/IHTTPSocketHandler.java | 7 + .../httpserver/adapter/IHTTPSocketServer.java | 14 + .../adapter/jetty/JettyHTTPSocket.java | 113 ++++++ .../adapter/jetty/JettyHTTPSocketServer.java | 80 +++++ .../simple/SimpleHTTPSocket.java} | 36 +- .../simple/SimpleHTTPSocketServer.java | 63 ++++ .../httpserver/handler/StaticFileHandler.java | 2 +- .../httpserver/handler/WebSocketHandler.java | 4 +- .../httpserver/helper/HttpMethod.java | 15 - .../httpserver/helper/JettyNoLog.java | 64 ---- .../router/DefaultRouteAutoInjector.java | 4 +- .../javawebstack/httpserver/router/Route.java | 10 +- .../httpserver/router/RouteBinder.java | 56 +-- .../httpserver/socket/HTTPServerSocket.java | 29 -- .../httpserver/socket/HTTPSocketWorker.java | 51 --- .../httpserver/test/HTTPTest.java | 31 +- .../test/MockHttpServletRequest.java | 325 ------------------ .../test/MockHttpServletResponse.java | 178 ---------- .../test/MockServletInputStream.java | 46 --- .../test/MockServletOutputStream.java | 32 -- .../httpserver/test/TestExchange.java | 107 +++--- .../httpserver/test/TestHTTPSocket.java | 121 +++++++ .../httpserver/{helper => util}/MimeType.java | 2 +- .../util/websocket/WebSocketFrame.java | 140 ++++++++ .../util/websocket/WebSocketUtil.java | 104 ++++++ .../websocket/InternalWebSocketAdapter.java | 56 --- .../InternalWebSocketRequestHandler.java | 49 ++- .../httpserver/websocket/WebSocket.java | 32 +- 33 files changed, 973 insertions(+), 1077 deletions(-) create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocket.java create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketHandler.java create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java rename src/main/java/org/javawebstack/httpserver/{socket/HTTPSocket.java => adapter/simple/SimpleHTTPSocket.java} (81%) create mode 100644 src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java delete mode 100644 src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java delete mode 100644 src/main/java/org/javawebstack/httpserver/helper/JettyNoLog.java delete mode 100644 src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java delete mode 100644 src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java delete mode 100644 src/main/java/org/javawebstack/httpserver/test/MockHttpServletRequest.java delete mode 100644 src/main/java/org/javawebstack/httpserver/test/MockHttpServletResponse.java delete mode 100644 src/main/java/org/javawebstack/httpserver/test/MockServletInputStream.java delete mode 100644 src/main/java/org/javawebstack/httpserver/test/MockServletOutputStream.java create mode 100644 src/main/java/org/javawebstack/httpserver/test/TestHTTPSocket.java rename src/main/java/org/javawebstack/httpserver/{helper => util}/MimeType.java (98%) create mode 100644 src/main/java/org/javawebstack/httpserver/util/websocket/WebSocketFrame.java create mode 100644 src/main/java/org/javawebstack/httpserver/util/websocket/WebSocketUtil.java delete mode 100644 src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketAdapter.java diff --git a/src/main/java/org/javawebstack/httpserver/Exchange.java b/src/main/java/org/javawebstack/httpserver/Exchange.java index 67dc768..0fd04ef 100644 --- a/src/main/java/org/javawebstack/httpserver/Exchange.java +++ b/src/main/java/org/javawebstack/httpserver/Exchange.java @@ -1,21 +1,20 @@ package org.javawebstack.httpserver; import org.javawebstack.abstractdata.*; -import org.javawebstack.httpserver.helper.HttpMethod; -import org.javawebstack.httpserver.helper.MimeType; +import org.javawebstack.httpserver.adapter.IHTTPSocket; +import org.javawebstack.httpserver.util.MimeType; import org.javawebstack.validator.ValidationContext; import org.javawebstack.validator.ValidationException; import org.javawebstack.validator.ValidationResult; import org.javawebstack.validator.Validator; -import javax.servlet.MultipartConfigElement; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class Exchange { @@ -26,22 +25,18 @@ public static Exchange current() { } private final HTTPServer server; - private final HttpMethod method; - private final String path; + private final HTTPMethod method; private byte[] body = null; private final Map pathVariables = new HashMap<>(); private final AbstractObject queryParameters; - private final HttpServletRequest request; - private final HttpServletResponse response; + private final IHTTPSocket socket; private final Map attributes = new HashMap<>(); - public Exchange(HTTPServer server, HttpServletRequest request, HttpServletResponse response) { + public Exchange(HTTPServer server, IHTTPSocket socket) { this.server = server; - this.request = request; - this.response = response; - this.path = request.getPathInfo(); - this.method = "websocket".equalsIgnoreCase(request.getHeader("Upgrade")) ? HttpMethod.WEBSOCKET : HttpMethod.valueOf(request.getMethod()); - this.queryParameters = AbstractElement.fromFormData(request.getQueryString()).object(); + this.socket = socket; + this.method = "websocket".equalsIgnoreCase(socket.getRequestHeader("upgrade")) ? HTTPMethod.WEBSOCKET : socket.getRequestMethod(); + this.queryParameters = AbstractElement.fromFormData(socket.getRequestQuery()).object(); } public T body(Class clazz) { @@ -91,22 +86,23 @@ public HTTPServer getServer() { return server; } - public HttpMethod getMethod() { + public HTTPMethod getMethod() { return method; } public String getPath() { - return path; + return socket.getRequestPath(); } public String getContentType() { - return request.getContentType() != null ? request.getContentType() : ""; + String contentType = socket.getRequestHeader("content-type"); + return contentType != null ? contentType : ""; } public byte[] read() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - InputStream is = request.getInputStream(); + InputStream is = socket.getInputStream(); byte[] data = new byte[1024]; int r; while (is.available() > 0) { @@ -126,8 +122,8 @@ public Exchange write(String data) { public Exchange write(byte[] bytes) { try { - response.getOutputStream().write(bytes); - response.getOutputStream().flush(); + socket.getOutputStream().write(bytes); + socket.getOutputStream().flush(); } catch (IOException ignored) { } return this; @@ -135,8 +131,8 @@ public Exchange write(byte[] bytes) { public Exchange write(byte[] bytes, int offset, int length) { try { - response.getOutputStream().write(bytes, offset, length); - response.getOutputStream().flush(); + socket.getOutputStream().write(bytes, offset, length); + socket.getOutputStream().flush(); } catch (IOException ignored) { } return this; @@ -153,49 +149,50 @@ public Exchange write(InputStream stream) throws IOException { public Exchange close() { try { - response.getOutputStream().close(); + socket.close(); } catch (IOException ignored) { } return this; } public Exchange header(String header, String value) { - if (header.equalsIgnoreCase("content-type")) { - response.setContentType(value); - return this; - } - response.setHeader(header, value); + socket.setResponseHeader(header, value); return this; } public Exchange status(int code) { - response.setStatus(code); + socket.setResponseStatus(code); return this; } public String header(String header) { - return request.getHeader(header); + return socket.getRequestHeader(header); } public Exchange redirect(String url) { - response.setStatus(302); + socket.setResponseStatus(302); + socket.setResponseHeader("location", url); try { - response.sendRedirect(url); - } catch (IOException ex) { - throw new RuntimeException(ex); + socket.writeHeaders(); + } catch (IOException e) { + e.printStackTrace(); } return this; } public List locales() { - return Collections.list(request.getLocales()); + String locale = socket.getRequestHeader("locale"); + if(locale == null) + return new ArrayList<>(); + return Stream.of(locale.split(" ?,")).map(s -> s.split(";")[0]).map(Locale::forLanguageTag).collect(Collectors.toList()); } public Locale locale(Locale... possible) { + List requested = locales(); if (possible.length == 0) - return request.getLocale(); + return requested.size() > 0 ? requested.get(0) : null; List possibleList = Arrays.asList(possible); - for (Locale l : locales()) { + for (Locale l : requested) { if (possibleList.contains(l)) return l; } @@ -209,15 +206,11 @@ public Exchange contentType(MimeType type) { public Exchange contentType(String contentType) { if (contentType == null || contentType.equals("")) return contentType("text/plain"); - return header("Content-Type", contentType); - } - - public HttpServletRequest rawRequest() { - return request; + return header("content-type", contentType); } - public HttpServletResponse rawResponse() { - return response; + public IHTTPSocket socket() { + return socket; } public T attrib(String key) { @@ -255,7 +248,7 @@ public T query(String name, Class type, T defaultValue) { } public String remoteAddr() { - return request.getRemoteAddr(); + return socket.getRemoteAddress(); } public Map getPathVariables() { @@ -299,24 +292,4 @@ protected static AbstractElement getPathElement(AbstractElement source, String p return getPathElement(getPathElement(source, spl[0]), path.substring(spl[0].length() + 1)); } - public Exchange enableMultipart() { - enableMultipart(System.getProperty("java.io.tmpdir")); - return this; - } - - public Exchange enableMultipart(String location) { - enableMultipart(location, -1L); - return this; - } - - public Exchange enableMultipart(String location, long maxFileSize) { - enableMultipart(location, maxFileSize, 1_048_576); - return this; - } - - - public Exchange enableMultipart(String location, long maxFileSize, int fileSizeThreshold) { - request.setAttribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement(location, maxFileSize, -1L, fileSizeThreshold)); - return this; - } } diff --git a/src/main/java/org/javawebstack/httpserver/HTTPMethod.java b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java index e5fdad6..6bc93f4 100644 --- a/src/main/java/org/javawebstack/httpserver/HTTPMethod.java +++ b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java @@ -10,6 +10,7 @@ public enum HTTPMethod { HEAD, OPTIONS, TRACE, - CONNECT + CONNECT, + WEBSOCKET } diff --git a/src/main/java/org/javawebstack/httpserver/HTTPServer.java b/src/main/java/org/javawebstack/httpserver/HTTPServer.java index bd7f498..04cefb7 100644 --- a/src/main/java/org/javawebstack/httpserver/HTTPServer.java +++ b/src/main/java/org/javawebstack/httpserver/HTTPServer.java @@ -1,15 +1,10 @@ package org.javawebstack.httpserver; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.javawebstack.abstractdata.AbstractMapper; import org.javawebstack.abstractdata.NamingPolicy; +import org.javawebstack.httpserver.adapter.IHTTPSocketServer; +import org.javawebstack.httpserver.adapter.simple.SimpleHTTPSocketServer; import org.javawebstack.httpserver.handler.*; -import org.javawebstack.httpserver.helper.HttpMethod; -import org.javawebstack.httpserver.helper.JettyNoLog; import org.javawebstack.httpserver.router.DefaultRouteAutoInjector; import org.javawebstack.httpserver.router.Route; import org.javawebstack.httpserver.router.RouteAutoInjector; @@ -20,12 +15,9 @@ import org.javawebstack.httpserver.transformer.route.RouteParamTransformerProvider; import org.javawebstack.httpserver.util.DirectoryFileProvider; import org.javawebstack.httpserver.util.ResourceFileProvider; -import org.javawebstack.httpserver.websocket.InternalWebSocketAdapter; import org.javawebstack.httpserver.websocket.InternalWebSocketRequestHandler; import org.reflections.Reflections; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.*; @@ -44,17 +36,20 @@ public class HTTPServer implements RouteParamTransformerProvider { private ExceptionHandler exceptionHandler = new ExceptionHandler.DefaultExceptionHandler(); private final List beforeRoutes = new ArrayList<>(); private final List afterRoutes = new ArrayList<>(); - private Server server; - private int port = 80; + private final IHTTPSocketServer server; private final List beforeInterceptors = new ArrayList<>(); private AbstractMapper abstractMapper = new AbstractMapper().setNamingPolicy(NamingPolicy.SNAKE_CASE); - private org.eclipse.jetty.websocket.server.WebSocketHandler webSocketHandler; - private List routeAutoInjectors = new ArrayList<>(); + private final List routeAutoInjectors = new ArrayList<>(); private final Map beforeMiddleware = new HashMap<>(); private final Map afterMiddleware = new HashMap<>(); private Function, Object> controllerInitiator = this::defaultControllerInitiator; public HTTPServer() { + this(new SimpleHTTPSocketServer()); + } + + public HTTPServer(IHTTPSocketServer server) { + this.server = server; routeParamTransformers.add(DefaultRouteParamTransformer.INSTANCE); routeAutoInjectors.add(DefaultRouteAutoInjector.INSTANCE); } @@ -88,43 +83,43 @@ public HTTPServer routeAutoInjector(RouteAutoInjector injector) { } public HTTPServer get(String pattern, RequestHandler... handlers) { - return route(HttpMethod.GET, pattern, handlers); + return route(HTTPMethod.GET, pattern, handlers); } public HTTPServer beforeGet(String pattern, RequestHandler... handlers) { - return beforeRoute(HttpMethod.GET, pattern, handlers); + return beforeRoute(HTTPMethod.GET, pattern, handlers); } public HTTPServer afterGet(String pattern, AfterRequestHandler... handlers) { - return afterRoute(HttpMethod.GET, pattern, handlers); + return afterRoute(HTTPMethod.GET, pattern, handlers); } public HTTPServer post(String pattern, RequestHandler... handlers) { - return route(HttpMethod.POST, pattern, handlers); + return route(HTTPMethod.POST, pattern, handlers); } public HTTPServer beforePost(String pattern, RequestHandler... handlers) { - return beforeRoute(HttpMethod.POST, pattern, handlers); + return beforeRoute(HTTPMethod.POST, pattern, handlers); } public HTTPServer afterPost(String pattern, AfterRequestHandler... handlers) { - return afterRoute(HttpMethod.POST, pattern, handlers); + return afterRoute(HTTPMethod.POST, pattern, handlers); } public HTTPServer put(String pattern, RequestHandler... handlers) { - return route(HttpMethod.PUT, pattern, handlers); + return route(HTTPMethod.PUT, pattern, handlers); } public HTTPServer beforePut(String pattern, RequestHandler... handlers) { - return beforeRoute(HttpMethod.PUT, pattern, handlers); + return beforeRoute(HTTPMethod.PUT, pattern, handlers); } public HTTPServer afterPut(String pattern, AfterRequestHandler... handlers) { - return afterRoute(HttpMethod.PUT, pattern, handlers); + return afterRoute(HTTPMethod.PUT, pattern, handlers); } public HTTPServer delete(String pattern, RequestHandler... handlers) { - return route(HttpMethod.DELETE, pattern, handlers); + return route(HTTPMethod.DELETE, pattern, handlers); } public HTTPServer staticDirectory(String pathPrefix, File directory) { @@ -148,60 +143,60 @@ public HTTPServer staticHandler(String pathPrefix, StaticFileHandler handler) { } public HTTPServer beforeDelete(String pattern, RequestHandler... handlers) { - return beforeRoute(HttpMethod.DELETE, pattern, handlers); + return beforeRoute(HTTPMethod.DELETE, pattern, handlers); } public HTTPServer afterDelete(String pattern, AfterRequestHandler... handlers) { - return afterRoute(HttpMethod.DELETE, pattern, handlers); + return afterRoute(HTTPMethod.DELETE, pattern, handlers); } - public HTTPServer route(HttpMethod method, String pattern, RequestHandler... handlers) { + public HTTPServer route(HTTPMethod method, String pattern, RequestHandler... handlers) { routes.add(new Route(this, method, pattern, Arrays.asList(handlers))); return this; } - public HTTPServer beforeRoute(HttpMethod method, String pattern, RequestHandler... handlers) { + public HTTPServer beforeRoute(HTTPMethod method, String pattern, RequestHandler... handlers) { beforeRoutes.add(new Route(this, method, pattern, Arrays.asList(handlers))); return this; } - public HTTPServer afterRoute(HttpMethod method, String pattern, AfterRequestHandler... handlers) { + public HTTPServer afterRoute(HTTPMethod method, String pattern, AfterRequestHandler... handlers) { afterRoutes.add(new Route(this, method, pattern, null).setAfterHandlers(Arrays.asList(handlers))); return this; } - public HTTPServer route(HttpMethod[] methods, String pattern, RequestHandler... handlers) { - for (HttpMethod method : methods) + public HTTPServer route(HTTPMethod[] methods, String pattern, RequestHandler... handlers) { + for (HTTPMethod method : methods) route(method, pattern, handlers); return this; } - public HTTPServer beforeRoute(HttpMethod[] methods, String pattern, RequestHandler... handlers) { - for (HttpMethod method : methods) + public HTTPServer beforeRoute(HTTPMethod[] methods, String pattern, RequestHandler... handlers) { + for (HTTPMethod method : methods) beforeRoute(method, pattern, handlers); return this; } - public HTTPServer afterRoute(HttpMethod[] methods, String pattern, AfterRequestHandler... handlers) { - for (HttpMethod method : methods) + public HTTPServer afterRoute(HTTPMethod[] methods, String pattern, AfterRequestHandler... handlers) { + for (HTTPMethod method : methods) afterRoute(method, pattern, handlers); return this; } public HTTPServer any(String pattern, RequestHandler... handlers) { - return route(HttpMethod.values(), pattern, handlers); + return route(HTTPMethod.values(), pattern, handlers); } public HTTPServer beforeAny(String pattern, RequestHandler... handlers) { - return beforeRoute(HttpMethod.values(), pattern, handlers); + return beforeRoute(HTTPMethod.values(), pattern, handlers); } public HTTPServer afterAny(String pattern, AfterRequestHandler... handlers) { - return afterRoute(HttpMethod.values(), pattern, handlers); + return afterRoute(HTTPMethod.values(), pattern, handlers); } public HTTPServer webSocket(String pattern, WebSocketHandler handler) { - return route(HttpMethod.WEBSOCKET, pattern, new InternalWebSocketRequestHandler(handler)); + return route(HTTPMethod.WEBSOCKET, pattern, new InternalWebSocketRequestHandler(handler)); } public HTTPServer middleware(String name, RequestHandler handler) { @@ -272,24 +267,15 @@ public HTTPServer controller(String globalPrefix, Object controller) { } public HTTPServer port(int port) { - this.port = port; + server.setPort(port); return this; } public HTTPServer start() { - Log.setLog(new JettyNoLog()); - server = new Server(port); - server.setHandler(new HttpHandler()); - webSocketHandler = new org.eclipse.jetty.websocket.server.WebSocketHandler() { - public void configure(WebSocketServletFactory webSocketServletFactory) { - webSocketServletFactory.register(InternalWebSocketAdapter.class); - } - }; - webSocketHandler.setServer(server); + server.setHandler(socket -> execute(new Exchange(this, socket))); try { server.start(); - webSocketHandler.start(); - logger.info("HTTP-Server started on port " + port); + logger.info("HTTP-Server started on port " + server.getPort()); } catch (Exception ex) { throw new RuntimeException(ex); } @@ -297,11 +283,7 @@ public void configure(WebSocketServletFactory webSocketServletFactory) { } public void join() { - try { - server.join(); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } + server.join(); } public void stop() { @@ -350,7 +332,7 @@ public void execute(Exchange exchange) { exchange.getPathVariables().putAll(pathVariables); for (RequestHandler handler : route.getHandlers()) { response = handler.handle(exchange); - if (exchange.getMethod() == HttpMethod.WEBSOCKET) { + if (exchange.getMethod() == HTTPMethod.WEBSOCKET) { Exchange.exchanges.remove(); return; } @@ -377,7 +359,7 @@ public void execute(Exchange exchange) { } if (response != null) exchange.write(transformResponse(exchange, response)); - if (exchange.getMethod() != HttpMethod.WEBSOCKET) + if (exchange.getMethod() != HTTPMethod.WEBSOCKET) exchange.close(); Exchange.exchanges.remove(); return; @@ -399,7 +381,6 @@ public List getRouteParamTransformer() { public RouteParamTransformer getRouteParamTransformer(String type) { return routeParamTransformers.stream().filter(t -> t.canTransform(type)).findFirst().orElse(null); } - public RequestHandler getBeforeMiddleware(String name) { return beforeMiddleware.get(name); } @@ -423,16 +404,6 @@ public byte[] transformResponse(Exchange exchange, Object object) { return object.toString().getBytes(StandardCharsets.UTF_8); } - private class HttpHandler extends AbstractHandler { - public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { - execute(new Exchange(HTTPServer.this, httpServletRequest, httpServletResponse)); - } - } - - public org.eclipse.jetty.websocket.server.WebSocketHandler getInternalWebSocketHandler() { - return webSocketHandler; - } - public ExceptionHandler getExceptionHandler() { return exceptionHandler; } diff --git a/src/main/java/org/javawebstack/httpserver/HTTPStatus.java b/src/main/java/org/javawebstack/httpserver/HTTPStatus.java index e42c5c3..1b03a38 100644 --- a/src/main/java/org/javawebstack/httpserver/HTTPStatus.java +++ b/src/main/java/org/javawebstack/httpserver/HTTPStatus.java @@ -2,6 +2,10 @@ public enum HTTPStatus { + CONTINUE(100, "Continue"), + SWITCHING_PROTOCOLS(101, "Switching Protocols"), + PROCESSING(102, "Processing"), + OK(200, "OK"), CREATED(201, "Created"), ACCEPTED(202, "Accepted"), diff --git a/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocket.java new file mode 100644 index 0000000..bf4bc33 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocket.java @@ -0,0 +1,58 @@ +package org.javawebstack.httpserver.adapter; + +import org.javawebstack.httpserver.HTTPMethod; +import org.javawebstack.httpserver.HTTPStatus; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; + +public interface IHTTPSocket { + + InputStream getInputStream() throws IOException; + + OutputStream getOutputStream() throws IOException; + + void close() throws IOException; + + boolean isClosed(); + + default IHTTPSocket setResponseStatus(HTTPStatus status) { + return setResponseStatus(status.getStatus(), status.getMessage()); + } + + default IHTTPSocket setResponseStatus(int status) { + HTTPStatus s = HTTPStatus.byStatus(status); + return setResponseStatus(status, s != null ? s.getMessage() : "Unknown"); + } + + IHTTPSocket setResponseStatus(int status, String message); + + IHTTPSocket setResponseHeader(String name, String value); + + IHTTPSocket addResponseHeader(String name, String value); + + HTTPMethod getRequestMethod(); + + String getRequestPath(); + + String getRequestQuery(); + + String getRequestVersion(); + + Set getRequestHeaderNames(); + + String getRequestHeader(String name); + + List getRequestHeaders(String name); + + int getResponseStatus(); + + String getResponseStatusMessage(); + + void writeHeaders() throws IOException; + + String getRemoteAddress(); + +} diff --git a/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketHandler.java b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketHandler.java new file mode 100644 index 0000000..d21ba29 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketHandler.java @@ -0,0 +1,7 @@ +package org.javawebstack.httpserver.adapter; + +public interface IHTTPSocketHandler { + + void handle(IHTTPSocket socket); + +} diff --git a/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java new file mode 100644 index 0000000..1679146 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java @@ -0,0 +1,14 @@ +package org.javawebstack.httpserver.adapter; + +import java.io.IOException; + +public interface IHTTPSocketServer { + + void setPort(int port); + int getPort(); + void start() throws IOException; + void stop(); + void join(); + void setHandler(IHTTPSocketHandler handler); + +} diff --git a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java new file mode 100644 index 0000000..9c959ce --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java @@ -0,0 +1,113 @@ +package org.javawebstack.httpserver.adapter.jetty; + +import org.javawebstack.httpserver.HTTPMethod; +import org.javawebstack.httpserver.HTTPStatus; +import org.javawebstack.httpserver.adapter.IHTTPSocket; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class JettyHTTPSocket implements IHTTPSocket { + + private final HttpServletRequest request; + private final HttpServletResponse response; + private boolean closed; + + public JettyHTTPSocket(HttpServletRequest request, HttpServletResponse response) { + this.request = request; + this.response = response; + } + + public HttpServletRequest rawRequest() { + return request; + } + + public HttpServletResponse rawResponse() { + return response; + } + + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return response.getOutputStream(); + } + + public void close() throws IOException { + closed = true; + response.getOutputStream().close(); + } + + public boolean isClosed() { + return closed; + } + + public IHTTPSocket setResponseStatus(int status, String message) { + response.setStatus(status, message); + return this; + } + + public IHTTPSocket setResponseHeader(String name, String value) { + response.setHeader(name, value); + return this; + } + + public IHTTPSocket addResponseHeader(String name, String value) { + response.addHeader(name, value); + return this; + } + + public HTTPMethod getRequestMethod() { + return HTTPMethod.valueOf(request.getMethod()); + } + + public String getRequestPath() { + return request.getPathInfo(); + } + + public String getRequestQuery() { + return request.getQueryString(); + } + + public String getRequestVersion() { + return "HTTP/1.1"; + } + + public Set getRequestHeaderNames() { + return new HashSet<>(Collections.list(request.getHeaderNames())); + } + + public String getRequestHeader(String name) { + return request.getHeader(name); + } + + public List getRequestHeaders(String name) { + return Collections.list(request.getHeaders(name)); + } + + public int getResponseStatus() { + return response.getStatus(); + } + + public String getResponseStatusMessage() { + HTTPStatus status = HTTPStatus.byStatus(response.getStatus()); + return status == null ? null : status.getMessage(); + } + + public void writeHeaders() throws IOException { + response.flushBuffer(); + } + + public String getRemoteAddress() { + return request.getRemoteAddr(); + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java new file mode 100644 index 0000000..c0f1def --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java @@ -0,0 +1,80 @@ +package org.javawebstack.httpserver.adapter.jetty; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.javawebstack.httpserver.adapter.IHTTPSocketHandler; +import org.javawebstack.httpserver.adapter.IHTTPSocketServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.WebConnection; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class JettyHTTPSocketServer implements IHTTPSocketServer { + + private Server server; + private IHTTPSocketHandler handler; + private int port; + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return port; + } + + public void start() throws IOException { + server = new Server(port); + server.setHandler(new AbstractHandler() { + public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { + if("websocket".equals(httpServletRequest.getHeader("Upgrade"))) { + httpServletResponse.setStatus(400); + httpServletResponse.getOutputStream().write("Websockets are not supported by this server!".getBytes(StandardCharsets.UTF_8)); + httpServletResponse.getOutputStream().close(); + return; + } + handler.handle(new JettyHTTPSocket(httpServletRequest, httpServletResponse)); + } + }); + server.setErrorHandler(new ErrorHandler()); + try { + server.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void join() { + try { + server.join(); + } catch (InterruptedException e) {} + } + + public void setHandler(IHTTPSocketHandler handler) { + this.handler = handler; + } + + private static class DummyUpgradeHandler implements HttpUpgradeHandler { + public void init(WebConnection webConnection) { + + } + public void destroy() { + + } + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java similarity index 81% rename from src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java rename to src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java index 52ceb51..6d9ad27 100644 --- a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocket.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java @@ -1,7 +1,8 @@ -package org.javawebstack.httpserver.socket; +package org.javawebstack.httpserver.adapter.simple; import org.javawebstack.httpserver.HTTPMethod; import org.javawebstack.httpserver.HTTPStatus; +import org.javawebstack.httpserver.adapter.IHTTPSocket; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -11,7 +12,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; -public class HTTPSocket { +public class SimpleHTTPSocket implements IHTTPSocket { private final Socket socket; private final InputStream inputStream; @@ -26,7 +27,7 @@ public class HTTPSocket { private String responseStatusMessage = "OK"; private boolean headersSent; - public HTTPSocket(Socket socket) throws IOException { + public SimpleHTTPSocket(Socket socket) throws IOException { this.socket = socket; this.inputStream = socket.getInputStream(); this.outputStream = socket.getOutputStream(); @@ -79,27 +80,31 @@ public HTTPSocket(Socket socket) throws IOException { } } - public HTTPSocket setResponseStatus(HTTPStatus status) { + public String getRemoteAddress() { + return socket.getInetAddress().getHostAddress(); + } + + public SimpleHTTPSocket setResponseStatus(HTTPStatus status) { return setResponseStatus(status.getStatus(), status.getMessage()); } - public HTTPSocket setResponseStatus(int status) { + public SimpleHTTPSocket setResponseStatus(int status) { HTTPStatus s = HTTPStatus.byStatus(status); return setResponseStatus(status, s != null ? s.getMessage() : "Unknown"); } - public HTTPSocket setResponseStatus(int status, String message) { + public SimpleHTTPSocket setResponseStatus(int status, String message) { this.responseStatus = status; this.responseStatusMessage = message; return this; } - public HTTPSocket setResponseHeader(String name, String value) { + public SimpleHTTPSocket setResponseHeader(String name, String value) { responseHeaders.put(name.toLowerCase(Locale.ROOT), Arrays.asList(value)); return this; } - public HTTPSocket addResponseHeader(String name, String value) { + public SimpleHTTPSocket addResponseHeader(String name, String value) { responseHeaders.computeIfAbsent(name.toLowerCase(Locale.ROOT), h -> new ArrayList<>()).add(value); return this; } @@ -109,6 +114,8 @@ public void close() throws IOException { } public void writeHeaders() throws IOException { + if(headersSent) + return; headersSent = true; StringBuilder sb = new StringBuilder(requestVersion) .append(' ') @@ -146,12 +153,17 @@ public String getRequestVersion() { return requestVersion; } - public Map> getRequestHeaders() { - return requestHeaders; + public Set getRequestHeaderNames() { + return requestHeaders.keySet(); + } + + public String getRequestHeader(String name) { + List values = requestHeaders.get(name.toLowerCase(Locale.ROOT)); + return values == null || values.size() == 0 ? null : values.get(0); } - public Map> getResponseHeaders() { - return responseHeaders; + public List getRequestHeaders(String name) { + return requestHeaders.getOrDefault(name.toLowerCase(Locale.ROOT), Collections.emptyList()); } public int getResponseStatus() { diff --git a/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java new file mode 100644 index 0000000..91aaeef --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java @@ -0,0 +1,63 @@ +package org.javawebstack.httpserver.adapter.simple; + +import org.javawebstack.httpserver.adapter.IHTTPSocketHandler; +import org.javawebstack.httpserver.adapter.IHTTPSocketServer; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SimpleHTTPSocketServer implements IHTTPSocketServer { + + private final Thread schedulerThread; + private ExecutorService executorService; + private ServerSocket serverSocket; + private int port = 80; + private IHTTPSocketHandler handler; + + public SimpleHTTPSocketServer() { + this.schedulerThread = new Thread(() -> { + while (!serverSocket.isClosed()) { + try { + Socket socket = serverSocket.accept(); + SimpleHTTPSocket httpSocket = new SimpleHTTPSocket(socket); + executorService.execute(() -> handler.handle(httpSocket)); + } catch (IOException exception) {} + } + }); + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return port; + } + + public void setHandler(IHTTPSocketHandler handler) { + this.handler = handler; + } + + public void start() throws IOException { + this.serverSocket = new ServerSocket(port); + this.executorService = Executors.newCachedThreadPool(); + this.schedulerThread.start(); + } + + public void join() { + try { + schedulerThread.join(); + } catch (InterruptedException e) {} + } + + public void stop() { + this.executorService.shutdown(); + try { + this.serverSocket.close(); + } catch (IOException e) {} + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/handler/StaticFileHandler.java b/src/main/java/org/javawebstack/httpserver/handler/StaticFileHandler.java index 013f8d7..f6fb6a9 100644 --- a/src/main/java/org/javawebstack/httpserver/handler/StaticFileHandler.java +++ b/src/main/java/org/javawebstack/httpserver/handler/StaticFileHandler.java @@ -1,7 +1,7 @@ package org.javawebstack.httpserver.handler; import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.helper.MimeType; +import org.javawebstack.httpserver.util.MimeType; import org.javawebstack.httpserver.util.FileProvider; import java.io.IOException; diff --git a/src/main/java/org/javawebstack/httpserver/handler/WebSocketHandler.java b/src/main/java/org/javawebstack/httpserver/handler/WebSocketHandler.java index de6dacf..a194a40 100644 --- a/src/main/java/org/javawebstack/httpserver/handler/WebSocketHandler.java +++ b/src/main/java/org/javawebstack/httpserver/handler/WebSocketHandler.java @@ -7,5 +7,7 @@ public interface WebSocketHandler { void onMessage(WebSocket socket, String message); - void onClose(WebSocket socket, int code, String reason); + void onMessage(WebSocket socket, byte[] message); + + void onClose(WebSocket socket, Integer code, String reason); } diff --git a/src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java b/src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java deleted file mode 100644 index cb60e36..0000000 --- a/src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.javawebstack.httpserver.helper; - -public enum HttpMethod { - GET, - POST, - PUT, - PATCH, - DELETE, - HEAD, - OPTIONS, - CONNECT, - MOVE, - TRACE, - WEBSOCKET -} diff --git a/src/main/java/org/javawebstack/httpserver/helper/JettyNoLog.java b/src/main/java/org/javawebstack/httpserver/helper/JettyNoLog.java deleted file mode 100644 index 25dc314..0000000 --- a/src/main/java/org/javawebstack/httpserver/helper/JettyNoLog.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.javawebstack.httpserver.helper; - -import org.eclipse.jetty.util.log.AbstractLogger; -import org.eclipse.jetty.util.log.Logger; - -public class JettyNoLog extends AbstractLogger implements Logger { - - protected Logger newLogger(String s) { - return this; - } - - public String getName() { - return "NoLog"; - } - - public void warn(String s, Object... objects) { - - } - - public void warn(Throwable throwable) { - - } - - public void warn(String s, Throwable throwable) { - - } - - public void info(String s, Object... objects) { - - } - - public void info(Throwable throwable) { - - } - - public void info(String s, Throwable throwable) { - - } - - public boolean isDebugEnabled() { - return false; - } - - public void setDebugEnabled(boolean b) { - - } - - public void debug(String s, Object... objects) { - - } - - public void debug(Throwable throwable) { - - } - - public void debug(String s, Throwable throwable) { - - } - - public void ignore(Throwable throwable) { - - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/router/DefaultRouteAutoInjector.java b/src/main/java/org/javawebstack/httpserver/router/DefaultRouteAutoInjector.java index 09dc6c3..54a2afe 100644 --- a/src/main/java/org/javawebstack/httpserver/router/DefaultRouteAutoInjector.java +++ b/src/main/java/org/javawebstack/httpserver/router/DefaultRouteAutoInjector.java @@ -1,7 +1,7 @@ package org.javawebstack.httpserver.router; import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.helper.HttpMethod; +import org.javawebstack.httpserver.HTTPMethod; import org.javawebstack.httpserver.websocket.WebSocket; import java.util.Map; @@ -14,7 +14,7 @@ public Object getValue(Exchange exchange, Map extraArgs, Class variables = new HashMap<>(); private List handlers; private List afterHandlers; - public Route(RouteParamTransformerProvider routeParamTransformerProvider, HttpMethod method, String pattern, List handlers) { + public Route(RouteParamTransformerProvider routeParamTransformerProvider, HTTPMethod method, String pattern, List handlers) { this(routeParamTransformerProvider, method, pattern, ":", handlers); } - public Route(RouteParamTransformerProvider routeParamTransformerProvider, HttpMethod method, String pattern, String variableDelimiter, List handlers) { + public Route(RouteParamTransformerProvider routeParamTransformerProvider, HTTPMethod method, String pattern, String variableDelimiter, List handlers) { this.handlers = handlers; this.method = method; this.routeParamTransformerProvider = routeParamTransformerProvider; @@ -95,7 +95,7 @@ public Map match(Exchange exchange) { return match(exchange, exchange.getMethod(), exchange.getPath()); } - public Map match(Exchange exchange, HttpMethod method, String path) { + public Map match(Exchange exchange, HTTPMethod method, String path) { if (this.method != method) return null; Matcher matcher = pattern.matcher(path); diff --git a/src/main/java/org/javawebstack/httpserver/router/RouteBinder.java b/src/main/java/org/javawebstack/httpserver/router/RouteBinder.java index d1fcf33..efe7f9d 100644 --- a/src/main/java/org/javawebstack/httpserver/router/RouteBinder.java +++ b/src/main/java/org/javawebstack/httpserver/router/RouteBinder.java @@ -1,11 +1,11 @@ package org.javawebstack.httpserver.router; import org.javawebstack.httpserver.Exchange; +import org.javawebstack.httpserver.HTTPMethod; import org.javawebstack.httpserver.HTTPServer; import org.javawebstack.httpserver.handler.AfterRequestHandler; import org.javawebstack.httpserver.handler.RequestHandler; import org.javawebstack.httpserver.handler.WebSocketHandler; -import org.javawebstack.httpserver.helper.HttpMethod; import org.javawebstack.httpserver.router.annotation.PathPrefix; import org.javawebstack.httpserver.router.annotation.With; import org.javawebstack.httpserver.router.annotation.params.*; @@ -33,10 +33,10 @@ public void bind(String globalPrefix, Object controller) { prefixes.add(""); With with = Arrays.stream(controller.getClass().getDeclaredAnnotationsByType(With.class)).findFirst().orElse(null); class Bind { - final HttpMethod method; + final HTTPMethod method; final String path; - public Bind(HttpMethod method, String path) { + public Bind(HTTPMethod method, String path) { this.method = method; this.path = path; } @@ -54,41 +54,41 @@ public Bind(HttpMethod method, String path) { // Registering HTTP-Method annotations. //region Registering HTTP-Method Annotations for (Get a : getAnnotations(Get.class, method)) { - bindMiddlewares(HttpMethod.GET, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.GET, a.value())); + bindMiddlewares(HTTPMethod.GET, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.GET, a.value())); } for (Post a : getAnnotations(Post.class, method)) { - bindMiddlewares(HttpMethod.POST, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.POST, a.value())); + bindMiddlewares(HTTPMethod.POST, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.POST, a.value())); } for (Put a : getAnnotations(Put.class, method)) { - bindMiddlewares(HttpMethod.PUT, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.PUT, a.value())); + bindMiddlewares(HTTPMethod.PUT, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.PUT, a.value())); } for (Delete a : getAnnotations(Delete.class, method)) { - bindMiddlewares(HttpMethod.DELETE, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.DELETE, a.value())); + bindMiddlewares(HTTPMethod.DELETE, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.DELETE, a.value())); } for (Patch a : getAnnotations(Patch.class, method)) { - bindMiddlewares(HttpMethod.PATCH, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.PATCH, a.value())); + bindMiddlewares(HTTPMethod.PATCH, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.PATCH, a.value())); } for (Trace a : getAnnotations(Trace.class, method)) { - bindMiddlewares(HttpMethod.TRACE, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.TRACE, a.value())); + bindMiddlewares(HTTPMethod.TRACE, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.TRACE, a.value())); } for (Options a : getAnnotations(Options.class, method)) { - bindMiddlewares(HttpMethod.OPTIONS, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.OPTIONS, a.value())); + bindMiddlewares(HTTPMethod.OPTIONS, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.OPTIONS, a.value())); } for (Head a : getAnnotations(Head.class, method)) { - bindMiddlewares(HttpMethod.HEAD, globalPrefix, prefixes, a.value(), middlewares); - binds.add(new Bind(HttpMethod.HEAD, a.value())); + bindMiddlewares(HTTPMethod.HEAD, globalPrefix, prefixes, a.value(), middlewares); + binds.add(new Bind(HTTPMethod.HEAD, a.value())); } for (WebSocketMessage a : getAnnotations(WebSocketMessage.class, method)) { WebSocketBindHandler handler = websocketHandlers.get(a.name()); if (handler == null) { - bindMiddlewares(HttpMethod.GET, globalPrefix, prefixes, a.value(), middlewares); + bindMiddlewares(HTTPMethod.GET, globalPrefix, prefixes, a.value(), middlewares); handler = new WebSocketBindHandler(); for (String prefix : prefixes) server.webSocket(buildPattern(globalPrefix, prefix, a.value()), handler); @@ -99,7 +99,7 @@ public Bind(HttpMethod method, String path) { for (WebSocketConnect a : getAnnotations(WebSocketConnect.class, method)) { WebSocketBindHandler handler = websocketHandlers.get(a.name()); if (handler == null) { - bindMiddlewares(HttpMethod.GET, globalPrefix, prefixes, a.value(), middlewares); + bindMiddlewares(HTTPMethod.GET, globalPrefix, prefixes, a.value(), middlewares); handler = new WebSocketBindHandler(); for (String prefix : prefixes) server.webSocket(buildPattern(globalPrefix, prefix, a.value()), handler); @@ -110,7 +110,7 @@ public Bind(HttpMethod method, String path) { for (WebSocketClose a : getAnnotations(WebSocketClose.class, method)) { WebSocketBindHandler handler = websocketHandlers.get(a.name()); if (handler == null) { - bindMiddlewares(HttpMethod.GET, globalPrefix, prefixes, a.value(), middlewares); + bindMiddlewares(HTTPMethod.GET, globalPrefix, prefixes, a.value(), middlewares); handler = new WebSocketBindHandler(); for (String prefix : prefixes) server.webSocket(buildPattern(globalPrefix, prefix, a.value()), handler); @@ -131,7 +131,7 @@ public Bind(HttpMethod method, String path) { } } - private void bindMiddlewares(HttpMethod method, String globalPrefix, List prefixes, String path, List middlewares) { + private void bindMiddlewares(HTTPMethod method, String globalPrefix, List prefixes, String path, List middlewares) { for (String name : middlewares) { RequestHandler before = server.getBeforeMiddleware(name); AfterRequestHandler after = server.getAfterMiddleware(name); @@ -279,7 +279,15 @@ public void onMessage(WebSocket socket, String message) { }}); } - public void onClose(WebSocket socket, int code, String reason) { + public void onMessage(WebSocket socket, byte[] message) { + if (messageHandler != null) + messageHandler.invoke(socket.getExchange(), new HashMap() {{ + put("websocket", socket); + put("websocketMessage", message); + }}); + } + + public void onClose(WebSocket socket, Integer code, String reason) { if (closeHandler != null) closeHandler.invoke(socket.getExchange(), new HashMap() {{ put("websocket", socket); diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java b/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java deleted file mode 100644 index 1d4f766..0000000 --- a/src/main/java/org/javawebstack/httpserver/socket/HTTPServerSocket.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.javawebstack.httpserver.socket; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -public class HTTPServerSocket { - - private final ServerSocket serverSocket; - - public HTTPServerSocket(int port) throws IOException { - serverSocket = new ServerSocket(port); - } - - public void close() throws IOException { - serverSocket.close(); - } - - public boolean isClosed() { - return serverSocket.isClosed(); - } - - public HTTPSocket accept() throws IOException { - Socket socket = serverSocket.accept(); - return new HTTPSocket(socket); - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java b/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java deleted file mode 100644 index 11ded81..0000000 --- a/src/main/java/org/javawebstack/httpserver/socket/HTTPSocketWorker.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.javawebstack.httpserver.socket; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; - -public class HTTPSocketWorker { - - private final Thread schedulerThread; - private ExecutorService executorService; - private final HTTPServerSocket serverSocket; - - public HTTPSocketWorker(HTTPServerSocket serverSocket, Consumer handler) { - this.serverSocket = serverSocket; - this.schedulerThread = new Thread(() -> { - while (!serverSocket.isClosed()) { - try { - HTTPSocket socket = serverSocket.accept(); - executorService.execute(() -> { - try { - handler.accept(socket); - if(!socket.isClosed()) - socket.close(); - } catch (IOException ex) {} - }); - } catch (IOException exception) {} - } - }); - } - - public HTTPSocketWorker start() { - this.executorService = Executors.newCachedThreadPool(); - this.schedulerThread.start(); - return this; - } - - public void join() { - try { - schedulerThread.join(); - } catch (InterruptedException e) {} - } - - public void stop() { - this.executorService.shutdown(); - try { - this.serverSocket.close(); - } catch (IOException e) {} - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/test/HTTPTest.java b/src/main/java/org/javawebstack/httpserver/test/HTTPTest.java index 3b9b3ec..375b94d 100644 --- a/src/main/java/org/javawebstack/httpserver/test/HTTPTest.java +++ b/src/main/java/org/javawebstack/httpserver/test/HTTPTest.java @@ -1,9 +1,13 @@ package org.javawebstack.httpserver.test; +import org.javawebstack.httpserver.HTTPMethod; import org.javawebstack.httpserver.HTTPServer; -import org.javawebstack.httpserver.helper.HttpMethod; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; public abstract class HTTPTest { @@ -28,7 +32,7 @@ public void setBearerToken(String token) { } public TestExchange httpGet(String url) { - return httpRequest(HttpMethod.GET, url, null); + return httpRequest(HTTPMethod.GET, url, null); } public TestExchange httpPost(String url) { @@ -36,7 +40,7 @@ public TestExchange httpPost(String url) { } public TestExchange httpPost(String url, Object content) { - return httpRequest(HttpMethod.POST, url, content); + return httpRequest(HTTPMethod.POST, url, content); } public TestExchange httpPut(String url) { @@ -44,7 +48,7 @@ public TestExchange httpPut(String url) { } public TestExchange httpPut(String url, Object content) { - return httpRequest(HttpMethod.PUT, url, content); + return httpRequest(HTTPMethod.PUT, url, content); } public TestExchange httpDelete(String url) { @@ -52,25 +56,22 @@ public TestExchange httpDelete(String url) { } public TestExchange httpDelete(String url, Object content) { - return httpRequest(HttpMethod.DELETE, url, content); + return httpRequest(HTTPMethod.DELETE, url, content); } - public TestExchange httpRequest(HttpMethod method, String url, Object content) { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(method); - request.setPath(url); - defaultHeaders.forEach(request::addHeader); + public TestExchange httpRequest(HTTPMethod method, String url, Object content) { + TestHTTPSocket socket = new TestHTTPSocket(method, url); + defaultHeaders.forEach((k, v) -> socket.getRequestHeaders().put(k.toLowerCase(Locale.ROOT), Collections.singletonList(v))); if (content != null) { if (content instanceof String) { - request.setContent((String) content); + socket.setInputStream(new ByteArrayInputStream(((String) content).getBytes(StandardCharsets.UTF_8))); } else if (content instanceof byte[]) { - request.setContent((byte[]) content); + socket.setInputStream(new ByteArrayInputStream((byte[]) content)); } else { - request.setContent(server.getAbstractMapper().toAbstract(content).toJsonString()); + socket.setInputStream(new ByteArrayInputStream(server.getAbstractMapper().toAbstract(content).toJsonString().getBytes(StandardCharsets.UTF_8))); } } - MockHttpServletResponse response = new MockHttpServletResponse(); - TestExchange exchange = new TestExchange(server, request, response); + TestExchange exchange = new TestExchange(server, socket); server.execute(exchange); return exchange; } diff --git a/src/main/java/org/javawebstack/httpserver/test/MockHttpServletRequest.java b/src/main/java/org/javawebstack/httpserver/test/MockHttpServletRequest.java deleted file mode 100644 index 969b19f..0000000 --- a/src/main/java/org/javawebstack/httpserver/test/MockHttpServletRequest.java +++ /dev/null @@ -1,325 +0,0 @@ -package org.javawebstack.httpserver.test; - -import org.javawebstack.httpserver.helper.HttpMethod; - -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.util.*; - -public class MockHttpServletRequest implements HttpServletRequest { - - private MockServletInputStream inputStream = new MockServletInputStream(); - private final Map headers = new HashMap<>(); - private String method = "GET"; - private String pathInfo = "/"; - private String queryString = ""; - - public void setContent(byte[] content) { - inputStream = new MockServletInputStream(content); - } - - public void setContent(String content) { - setContent(content.getBytes(StandardCharsets.UTF_8)); - } - - public void addHeader(String key, String value) { - headers.put(key, value); - } - - public void setMethod(HttpMethod method) { - this.method = method.name(); - } - - public void setPath(String path) { - String[] spl = path.split("\\?"); - this.pathInfo = spl[0]; - queryString = spl.length > 1 ? spl[1] : ""; - } - - public String getAuthType() { - return null; - } - - public Cookie[] getCookies() { - return new Cookie[0]; - } - - public long getDateHeader(String s) { - return 0; - } - - public String getHeader(String s) { - return headers.get(s); - } - - public Enumeration getHeaders(String s) { - String header = getHeader(s); - Set set = new HashSet<>(); - if (header != null) - set.add(header); - return Collections.enumeration(set); - } - - public Enumeration getHeaderNames() { - return Collections.enumeration(headers.keySet()); - } - - public int getIntHeader(String s) { - return Integer.parseInt(getHeader(s)); - } - - public String getMethod() { - return method; - } - - public String getPathInfo() { - return pathInfo; - } - - public String getPathTranslated() { - return null; - } - - public String getContextPath() { - return null; - } - - public String getQueryString() { - return queryString; - } - - public String getRemoteUser() { - return null; - } - - public boolean isUserInRole(String s) { - return false; - } - - public Principal getUserPrincipal() { - return null; - } - - public String getRequestedSessionId() { - return null; - } - - public String getRequestURI() { - return null; - } - - public StringBuffer getRequestURL() { - return null; - } - - public String getServletPath() { - return null; - } - - public HttpSession getSession(boolean b) { - return null; - } - - public HttpSession getSession() { - return null; - } - - public String changeSessionId() { - return null; - } - - public boolean isRequestedSessionIdValid() { - return false; - } - - public boolean isRequestedSessionIdFromCookie() { - return false; - } - - public boolean isRequestedSessionIdFromURL() { - return false; - } - - public boolean isRequestedSessionIdFromUrl() { - return false; - } - - public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { - return false; - } - - public void login(String s, String s1) throws ServletException { - - } - - public void logout() throws ServletException { - - } - - public Collection getParts() throws IOException, ServletException { - return null; - } - - public Part getPart(String s) throws IOException, ServletException { - return null; - } - - public T upgrade(Class aClass) throws IOException, ServletException { - return null; - } - - public Object getAttribute(String s) { - return null; - } - - public Enumeration getAttributeNames() { - return null; - } - - public String getCharacterEncoding() { - return null; - } - - public void setCharacterEncoding(String s) throws UnsupportedEncodingException { - - } - - public int getContentLength() { - return inputStream.size(); - } - - public long getContentLengthLong() { - return getContentLength(); - } - - public String getContentType() { - return getHeader("Content-Type"); - } - - public ServletInputStream getInputStream() throws IOException { - return inputStream; - } - - public String getParameter(String s) { - return null; - } - - public Enumeration getParameterNames() { - return null; - } - - public String[] getParameterValues(String s) { - return new String[0]; - } - - public Map getParameterMap() { - return null; - } - - public String getProtocol() { - return null; - } - - public String getScheme() { - return null; - } - - public String getServerName() { - return null; - } - - public int getServerPort() { - return 80; - } - - public BufferedReader getReader() throws IOException { - return new BufferedReader(new InputStreamReader(inputStream)); - } - - public String getRemoteAddr() { - return "127.0.0.1"; - } - - public String getRemoteHost() { - return "localhost"; - } - - public void setAttribute(String s, Object o) { - - } - - public void removeAttribute(String s) { - - } - - public Locale getLocale() { - return Locale.ENGLISH; - } - - public Enumeration getLocales() { - return Collections.enumeration(Collections.singletonList(getLocale())); - } - - public boolean isSecure() { - return false; - } - - public RequestDispatcher getRequestDispatcher(String s) { - return null; - } - - public String getRealPath(String s) { - return null; - } - - public int getRemotePort() { - return 0; - } - - public String getLocalName() { - return "localhost"; - } - - public String getLocalAddr() { - return "127.0.0.1"; - } - - public int getLocalPort() { - return 80; - } - - public ServletContext getServletContext() { - return null; - } - - public AsyncContext startAsync() throws IllegalStateException { - return null; - } - - public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { - return null; - } - - public boolean isAsyncStarted() { - return false; - } - - public boolean isAsyncSupported() { - return false; - } - - public AsyncContext getAsyncContext() { - return null; - } - - public DispatcherType getDispatcherType() { - return null; - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/test/MockHttpServletResponse.java b/src/main/java/org/javawebstack/httpserver/test/MockHttpServletResponse.java deleted file mode 100644 index 5b33e84..0000000 --- a/src/main/java/org/javawebstack/httpserver/test/MockHttpServletResponse.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.javawebstack.httpserver.test; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.*; - -public class MockHttpServletResponse implements HttpServletResponse { - - private int status = 200; - private final MockServletOutputStream outputStream = new MockServletOutputStream(); - private final Map headers = new HashMap<>(); - - public void addCookie(Cookie cookie) { - - } - - public boolean containsHeader(String s) { - return headers.containsKey(s); - } - - public String encodeURL(String s) { - return null; - } - - public String encodeRedirectURL(String s) { - return null; - } - - public String encodeUrl(String s) { - return null; - } - - public String encodeRedirectUrl(String s) { - return null; - } - - public void sendError(int i, String s) throws IOException { - sendError(i); - } - - public void sendError(int i) throws IOException { - setStatus(i); - outputStream.close(); - } - - public void sendRedirect(String s) throws IOException { - setStatus(302); - setHeader("Location", s); - } - - public void setDateHeader(String s, long l) { - - } - - public void addDateHeader(String s, long l) { - - } - - public void setHeader(String s, String s1) { - headers.put(s, s1); - } - - public void addHeader(String s, String s1) { - headers.put(s, s1); - } - - public void setIntHeader(String s, int i) { - setHeader(s, String.valueOf(i)); - } - - public void addIntHeader(String s, int i) { - addHeader(s, String.valueOf(i)); - } - - public void setStatus(int i) { - this.status = i; - } - - public void setStatus(int i, String s) { - setStatus(i); - } - - public int getStatus() { - return status; - } - - public String getHeader(String s) { - return headers.get(s); - } - - public Collection getHeaders(String s) { - String header = getHeader(s); - Set set = new HashSet<>(); - if (header != null) - set.add(header); - return set; - } - - public Collection getHeaderNames() { - return headers.keySet(); - } - - public String getCharacterEncoding() { - return null; - } - - public String getContentType() { - return getHeader("Content-Type"); - } - - public ServletOutputStream getOutputStream() throws IOException { - return outputStream; - } - - public PrintWriter getWriter() throws IOException { - return new PrintWriter(outputStream); - } - - public void setCharacterEncoding(String s) { - - } - - public void setContentLength(int i) { - setIntHeader("Content-Length", i); - } - - public void setContentLengthLong(long l) { - setHeader("Content-Length", String.valueOf(l)); - } - - public void setContentType(String s) { - setHeader("Content-Type", s); - } - - public void setBufferSize(int i) { - - } - - public int getBufferSize() { - return 0; - } - - public void flushBuffer() throws IOException { - - } - - public void resetBuffer() { - - } - - public boolean isCommitted() { - return false; - } - - public void reset() { - - } - - public void setLocale(Locale locale) { - - } - - public Locale getLocale() { - return null; - } - - public byte[] getContent() { - return outputStream.getBytes(); - } - - public String getContentString() { - return outputStream.getString(); - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/test/MockServletInputStream.java b/src/main/java/org/javawebstack/httpserver/test/MockServletInputStream.java deleted file mode 100644 index e6dd2e4..0000000 --- a/src/main/java/org/javawebstack/httpserver/test/MockServletInputStream.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.javawebstack.httpserver.test; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -public class MockServletInputStream extends ServletInputStream { - - private final int size; - private final ByteArrayInputStream inputStream; - - public MockServletInputStream(byte[] bytes) { - inputStream = new ByteArrayInputStream(bytes); - size = bytes.length; - } - - public MockServletInputStream() { - this(new byte[0]); - } - - public boolean isFinished() { - return inputStream.available() == 0; - } - - public boolean isReady() { - return true; - } - - public int available() { - return inputStream.available(); - } - - public void setReadListener(ReadListener readListener) { - - } - - public int read() throws IOException { - return inputStream.read(); - } - - public int size() { - return size; - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/test/MockServletOutputStream.java b/src/main/java/org/javawebstack/httpserver/test/MockServletOutputStream.java deleted file mode 100644 index 49e341f..0000000 --- a/src/main/java/org/javawebstack/httpserver/test/MockServletOutputStream.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.javawebstack.httpserver.test; - -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; - -public class MockServletOutputStream extends ServletOutputStream { - - private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - public boolean isReady() { - return true; - } - - public void setWriteListener(WriteListener writeListener) { - - } - - public void write(int b) { - buffer.write(b); - } - - public byte[] getBytes() { - return buffer.toByteArray(); - } - - public String getString() { - return new String(getBytes(), StandardCharsets.UTF_8); - } - -} diff --git a/src/main/java/org/javawebstack/httpserver/test/TestExchange.java b/src/main/java/org/javawebstack/httpserver/test/TestExchange.java index 8c237b2..a7de3ba 100644 --- a/src/main/java/org/javawebstack/httpserver/test/TestExchange.java +++ b/src/main/java/org/javawebstack/httpserver/test/TestExchange.java @@ -3,20 +3,24 @@ import org.javawebstack.abstractdata.AbstractElement; import org.javawebstack.httpserver.Exchange; import org.javawebstack.httpserver.HTTPServer; -import org.javawebstack.httpserver.helper.MimeType; +import org.javawebstack.httpserver.adapter.IHTTPSocket; +import org.javawebstack.httpserver.util.MimeType; import org.junit.jupiter.api.Assertions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class TestExchange extends Exchange { - private MockHttpServletRequest mockReq; - private MockHttpServletResponse mockRes; + + private TestHTTPSocket testSocket; - public TestExchange(HTTPServer service, MockHttpServletRequest request, MockHttpServletResponse response) { - super(service, request, response); - this.mockReq = request; - this.mockRes = response; + public TestExchange(HTTPServer service, TestHTTPSocket socket) { + super(service, socket); + this.testSocket = socket; } public TestExchange print() { @@ -24,67 +28,67 @@ public TestExchange print() { return this; } - public MockHttpServletRequest mockRequest() { - return mockReq; - } - - public MockHttpServletResponse mockResponse() { - return mockRes; + public String getOutputString() { + try { + return new String(((ByteArrayOutputStream) testSocket.getOutputStream()).toByteArray(), StandardCharsets.UTF_8); + } catch (IOException ignored) { + return null; + } } public TestExchange printResponse() { - System.out.println("HTTP Response " + mockRes.getStatus()); - System.out.println(mockRes.getContentString()); + System.out.println("HTTP Response " + testSocket.getResponseStatus()); + System.out.println(getOutputString()); return this; } public TestExchange assertStatus(int status) { - Assertions.assertEquals(status, mockRes.getStatus()); + Assertions.assertEquals(status, testSocket.getResponseStatus()); return this; } public TestExchange assertStatus(int status, String message) { - Assertions.assertEquals(status, mockRes.getStatus(), message); + Assertions.assertEquals(status, testSocket.getResponseStatus(), message); return this; } public TestExchange assertNotStatus(int status) { - Assertions.assertNotEquals(status, mockRes.getStatus()); + Assertions.assertNotEquals(status, testSocket.getResponseStatus()); return this; } public TestExchange assertNotStatus(int status, String message) { - Assertions.assertNotEquals(status, mockRes.getStatus(), message); + Assertions.assertNotEquals(status, testSocket.getResponseStatus(), message); return this; } public TestExchange assertOk() { - Assertions.assertEquals(200, mockRes.getStatus()); + Assertions.assertEquals(200, testSocket.getResponseStatus()); return this; } public TestExchange assertOk(String message) { - Assertions.assertEquals(200, mockRes.getStatus(), message); + Assertions.assertEquals(200, testSocket.getResponseStatus(), message); return this; } public TestExchange assertCreated() { - Assertions.assertEquals(201, mockRes.getStatus()); + Assertions.assertEquals(201, testSocket.getResponseStatus()); return this; } public TestExchange assertCreated(String message) { - Assertions.assertEquals(201, mockRes.getStatus(), message); + Assertions.assertEquals(201, testSocket.getResponseStatus(), message); return this; } public TestExchange assertNoContent() { - Assertions.assertEquals(204, mockRes.getStatus()); + Assertions.assertEquals(204, testSocket.getResponseStatus()); return this; } public TestExchange assertNoContent(String message) { - Assertions.assertEquals(204, mockRes.getStatus(), message); + Assertions.assertEquals(204, testSocket.getResponseStatus(), message); return this; } @@ -100,93 +104,93 @@ public TestExchange assertRedirect(String message) { public TestExchange assertRedirectTo(String url) { assertRedirect(); - Assertions.assertEquals(url, mockRes.getHeader("Location")); + Assertions.assertEquals(url, testSocket.getResponseHeaders().get("location")); return this; } public TestExchange assertRedirectTo(String url, String message) { assertRedirect(message); - Assertions.assertEquals(url, mockRes.getHeader("Location"), message); + Assertions.assertEquals(url, testSocket.getResponseHeaders().get("location"), message); return this; } public TestExchange assertNotFound() { - Assertions.assertEquals(404, mockRes.getStatus()); + Assertions.assertEquals(404, testSocket.getResponseStatus()); return this; } public TestExchange assertNotFound(String message) { - Assertions.assertEquals(404, mockRes.getStatus(), message); + Assertions.assertEquals(404, testSocket.getResponseStatus(), message); return this; } public TestExchange assertBadRequest() { - Assertions.assertEquals(400, mockRes.getStatus()); + Assertions.assertEquals(400, testSocket.getResponseStatus()); return this; } public TestExchange assertBadRequest(String message) { - Assertions.assertEquals(400, mockRes.getStatus(), message); + Assertions.assertEquals(400, testSocket.getResponseStatus(), message); return this; } public TestExchange assertForbidden() { - Assertions.assertEquals(403, mockRes.getStatus()); + Assertions.assertEquals(403, testSocket.getResponseStatus()); return this; } public TestExchange assertForbidden(String message) { - Assertions.assertEquals(403, mockRes.getStatus(), message); + Assertions.assertEquals(403, testSocket.getResponseStatus(), message); return this; } public TestExchange assertUnauthorized() { - Assertions.assertEquals(401, mockRes.getStatus()); + Assertions.assertEquals(401, testSocket.getResponseStatus()); return this; } public TestExchange assertUnauthorized(String message) { - Assertions.assertEquals(401, mockRes.getStatus(), message); + Assertions.assertEquals(401, testSocket.getResponseStatus(), message); return this; } public TestExchange assertSuccess() { - Assertions.assertEquals(200, (mockRes.getStatus() / 100) * 100); + Assertions.assertEquals(200, (testSocket.getResponseStatus() / 100) * 100); return this; } public TestExchange assertSuccess(String message) { - Assertions.assertEquals(200, (mockRes.getStatus() / 100) * 100, message); + Assertions.assertEquals(200, (testSocket.getResponseStatus() / 100) * 100, message); return this; } public TestExchange assertError() { - Assertions.assertTrue(mockRes.getStatus() >= 400); + Assertions.assertTrue(testSocket.getResponseStatus() >= 400); return this; } public TestExchange assertError(String message) { - Assertions.assertTrue(mockRes.getStatus() >= 400, message); + Assertions.assertTrue(testSocket.getResponseStatus() >= 400, message); return this; } public TestExchange assertHeader(String key, String value) { - Assertions.assertEquals(value, mockRes.getHeader(key)); + Assertions.assertEquals(value, testSocket.getResponseHeaders().get(key.toLowerCase(Locale.ROOT))); return this; } public TestExchange assertHeader(String key, String value, String message) { - Assertions.assertEquals(value, mockRes.getHeader(key), message); + Assertions.assertEquals(value, testSocket.getResponseHeaders().get(key.toLowerCase(Locale.ROOT)), message); return this; } public TestExchange assertJsonPath(String path, Object value) { - Assertions.assertTrue(checkGraph(getPathElement(mockResponseBody(mockRes), path), value)); + Assertions.assertTrue(checkGraph(getPathElement(mockResponseBody(), path), value)); return this; } public TestExchange assertJsonPath(String path, Object value, String message) { - Assertions.assertTrue(checkGraph(getPathElement(mockResponseBody(mockRes), path), value), message); + Assertions.assertTrue(checkGraph(getPathElement(mockResponseBody(), path), value), message); return this; } @@ -201,26 +205,27 @@ public TestExchange assertJson(Object value, String message) { } public TestExchange assertBody(String content) { - Assertions.assertEquals(content, mockRes.getContentString()); + Assertions.assertEquals(content, getOutputString()); return this; } public TestExchange assertBody(String content, String message) { - Assertions.assertEquals(content, mockRes.getContentString(), message); + Assertions.assertEquals(content, getOutputString(), message); return this; } - private AbstractElement mockResponseBody(MockHttpServletResponse response) { - MimeType type = MimeType.byMimeType(response.getContentType()); + private AbstractElement mockResponseBody() { + List contentTypes = testSocket.getResponseHeaders().get("content-type"); + MimeType type = MimeType.byMimeType(contentTypes.size() > 0 ? contentTypes.get(0) : null); if (type == null) type = MimeType.JSON; switch (type) { default: - return AbstractElement.fromJson(response.getContentString()); + return AbstractElement.fromJson(getOutputString()); case YAML: - return AbstractElement.fromYaml(response.getContentString(), true); + return AbstractElement.fromYaml(getOutputString(), true); case X_WWW_FORM_URLENCODED: - return AbstractElement.fromFormData(response.getContentString()); + return AbstractElement.fromFormData(getOutputString()); } } @@ -280,6 +285,6 @@ private boolean isRedirect() { redirectCodes.add(307); redirectCodes.add(308); - return redirectCodes.contains(mockRes.getStatus()); + return redirectCodes.contains(testSocket.getResponseStatus()); } } diff --git a/src/main/java/org/javawebstack/httpserver/test/TestHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/test/TestHTTPSocket.java new file mode 100644 index 0000000..5dc1a70 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/test/TestHTTPSocket.java @@ -0,0 +1,121 @@ +package org.javawebstack.httpserver.test; + +import org.javawebstack.httpserver.HTTPMethod; +import org.javawebstack.httpserver.adapter.IHTTPSocket; + +import java.io.*; +import java.util.*; + +public class TestHTTPSocket implements IHTTPSocket { + + private final HTTPMethod requestMethod; + private final String requestPath; + private final String requestQuery; + private InputStream inputStream = new ByteArrayInputStream(new byte[0]); + private final OutputStream outputStream = new ByteArrayOutputStream(); + private boolean closed; + private int responseStatus = 200; + private String responseStatusMessage = "OK"; + private final Map> requestHeaders = new HashMap<>(); + private final Map> responseHeaders = new HashMap<>(); + + public Map> getRequestHeaders() { + return requestHeaders; + } + + public Map> getResponseHeaders() { + return responseHeaders; + } + + public TestHTTPSocket(HTTPMethod method, String url) { + this.requestMethod = method; + String[] pathSplit = url.split("\\?", 2); + requestPath = pathSplit[0]; + if(pathSplit.length == 2) + requestQuery = pathSplit[1]; + else + requestQuery = null; + } + + public TestHTTPSocket setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + return this; + } + + public InputStream getInputStream() throws IOException { + return inputStream; + } + + public OutputStream getOutputStream() throws IOException { + return outputStream; + } + + public void close() throws IOException { + closed = true; + } + + public boolean isClosed() { + return closed; + } + + public TestHTTPSocket setResponseStatus(int status, String message) { + this.responseStatus = status; + this.responseStatusMessage = message; + return this; + } + + public TestHTTPSocket setResponseHeader(String name, String value) { + responseHeaders.put(name.toLowerCase(Locale.ROOT), Arrays.asList(value)); + return this; + } + + public TestHTTPSocket addResponseHeader(String name, String value) { + responseHeaders.computeIfAbsent(name.toLowerCase(Locale.ROOT), h -> new ArrayList<>()).add(value); + return this; + } + + public HTTPMethod getRequestMethod() { + return requestMethod; + } + + public String getRequestPath() { + return requestPath; + } + + public String getRequestQuery() { + return requestQuery; + } + + public String getRequestVersion() { + return "HTTP/1.1"; + } + + public Set getRequestHeaderNames() { + return Collections.emptySet(); + } + + public String getRequestHeader(String name) { + return null; + } + + public List getRequestHeaders(String name) { + return null; + } + + public int getResponseStatus() { + return responseStatus; + } + + public String getResponseStatusMessage() { + return responseStatusMessage; + } + + public void writeHeaders() throws IOException { + + } + + public String getRemoteAddress() { + return null; + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/helper/MimeType.java b/src/main/java/org/javawebstack/httpserver/util/MimeType.java similarity index 98% rename from src/main/java/org/javawebstack/httpserver/helper/MimeType.java rename to src/main/java/org/javawebstack/httpserver/util/MimeType.java index 148853a..3703170 100644 --- a/src/main/java/org/javawebstack/httpserver/helper/MimeType.java +++ b/src/main/java/org/javawebstack/httpserver/util/MimeType.java @@ -1,4 +1,4 @@ -package org.javawebstack.httpserver.helper; +package org.javawebstack.httpserver.util; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/org/javawebstack/httpserver/util/websocket/WebSocketFrame.java b/src/main/java/org/javawebstack/httpserver/util/websocket/WebSocketFrame.java new file mode 100644 index 0000000..9c54c14 --- /dev/null +++ b/src/main/java/org/javawebstack/httpserver/util/websocket/WebSocketFrame.java @@ -0,0 +1,140 @@ +package org.javawebstack.httpserver.util.websocket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class WebSocketFrame { + + private byte flags; + private byte opcode; + private byte[] maskKey; + private byte[] payload; + + public boolean isFin() { + return (flags & 0b1000_0000) > 0; + } + + public WebSocketFrame setFin(boolean fin) { + flags = (byte) ((flags & 0b0111_1111) | ((fin ? 1 : 0) << 7)); + return this; + } + + public boolean isRsv1() { + return (flags & 0b0100_0000) > 0; + } + + public WebSocketFrame setRsv1(boolean rsv1) { + flags = (byte) ((flags & 0b1011_1111) | ((rsv1 ? 1 : 0) << 6)); + return this; + } + + public boolean isRsv2() { + return (flags & 0b0010_0000) > 0; + } + + public WebSocketFrame setRsv2(boolean rsv2) { + flags = (byte) ((flags & 0b1101_1111) | ((rsv2 ? 1 : 0) << 5)); + return this; + } + + public boolean isRsv3() { + return (flags & 0b0001_0000) > 0; + } + + public WebSocketFrame setRsv3(boolean rsv3) { + flags = (byte) ((flags & 0b1110_1111) | ((rsv3 ? 1 : 0) << 4)); + return this; + } + + public byte getOpcode() { + return opcode; + } + + public WebSocketFrame setOpcode(byte opcode) { + this.opcode = opcode; + return this; + } + + public byte[] getMaskKey() { + return maskKey; + } + + public WebSocketFrame setMaskKey(byte[] maskKey) { + this.maskKey = maskKey; + return this; + } + + public byte[] getPayload() { + return payload; + } + + public WebSocketFrame setPayload(byte[] payload) { + this.payload = payload; + return this; + } + + public void write(OutputStream stream) throws IOException { + stream.write(flags | opcode); + int lengthByte = payload.length > 125 ? (payload.length > 0xFFFF ? 127 : 126) : payload.length; + stream.write((maskKey != null ? 0b1000_0000 : 0) | lengthByte); + if(lengthByte == 127) { + stream.write(payload.length >> 24); + stream.write((payload.length & 0xFF0000) >> 16); + } + if(lengthByte > 125) { + stream.write((payload.length & 0xFF00) >> 8); + stream.write(payload.length & 0xFF); + } + if(maskKey != null) + stream.write(maskKey); + if(maskKey != null) { + for(int i=0; i> 7) == 1 ? new byte[4] : null; + int len = b & 0b0111_1111; + if(len == 126) { + len = safeRead(stream) << 8; + len |= safeRead(stream); + } else if(len == 127) { + len = safeRead(stream) << 24; + len |= safeRead(stream) << 16; + len |= safeRead(stream) << 8; + len |= safeRead(stream); + } + if(frame.maskKey != null) { + frame.maskKey[0] = (byte) safeRead(stream); + frame.maskKey[1] = (byte) safeRead(stream); + frame.maskKey[2] = (byte) safeRead(stream); + frame.maskKey[3] = (byte) safeRead(stream); + } + frame.payload = new byte[len]; + if(frame.maskKey != null) { + for(int i=0; i> 8); + payload[1] = (byte) (code & 0xF); + if(reasonBytes != null) + System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); + } + new WebSocketFrame().setFin(true).setOpcode(OP_CLOSE).setPayload(payload).write(socket.getOutputStream()); + socket.close(); + } + + public static ClosePayload parseClose(byte[] payload) { + ClosePayload close = new ClosePayload(); + if(payload.length >= 2) { + close.code = (payload[0] << 8) | payload[1]; + if(payload.length > 2) { + byte[] reasonBytes = new byte[payload.length - 2]; + System.arraycopy(payload, 2, reasonBytes, 0, reasonBytes.length); + close.reason = new String(reasonBytes, StandardCharsets.UTF_8); + } + } + return close; + } + + public static void send(IHTTPSocket socket, String message) throws IOException { + new WebSocketFrame().setFin(true).setOpcode(OP_TEXT).setPayload(message.getBytes(StandardCharsets.UTF_8)).write(socket.getOutputStream()); + } + + public static void send(IHTTPSocket socket, byte[] message) throws IOException { + new WebSocketFrame().setFin(true).setOpcode(OP_BINARY).setPayload(message).write(socket.getOutputStream()); + } + + public static class ClosePayload { + + private Integer code; + private String reason; + + public Integer getCode() { + return code; + } + + public String getReason() { + return reason; + } + } + + private static String calcKey(String key) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes(StandardCharsets.US_ASCII)); + return new String(Base64.getEncoder().encode(digest.digest()), StandardCharsets.US_ASCII); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketAdapter.java b/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketAdapter.java deleted file mode 100644 index 87436b6..0000000 --- a/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketAdapter.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.javawebstack.httpserver.websocket; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; - -import java.util.HashMap; -import java.util.Map; - -@org.eclipse.jetty.websocket.api.annotations.WebSocket -public class InternalWebSocketAdapter { - public static Map webSockets = new HashMap<>(); - - @OnWebSocketConnect - public void onConnect(Session session) { - WebSocket socket = webSockets.get(session.getUpgradeResponse().getHeader("X-Server-WSID")); - if (socket == null) { - session.close(500, "Server Error"); - return; - } - socket.setSession(session); - try { - socket.getHandler().onConnect(socket); - } catch (Throwable t) { - socket.getExchange().getServer().getExceptionHandler().handle(socket.getExchange(), t); - } - } - - @OnWebSocketMessage - public void onMessage(Session session, String message) { - WebSocket socket = webSockets.get(session.getUpgradeResponse().getHeader("X-Server-WSID")); - if (socket == null) { - session.close(500, "Server Error"); - return; - } - try { - socket.getHandler().onMessage(socket, message); - } catch (Throwable t) { - socket.getExchange().getServer().getExceptionHandler().handle(socket.getExchange(), t); - } - } - - @OnWebSocketClose - public void onClose(Session session, int code, String reason) { - WebSocket socket = webSockets.get(session.getUpgradeResponse().getHeader("X-Server-WSID")); - if (socket != null) { - try { - socket.getHandler().onClose(socket, code, reason); - } catch (Throwable t) { - socket.getExchange().getServer().getExceptionHandler().handle(socket.getExchange(), t); - } - webSockets.remove(session.getUpgradeResponse().getHeader("X-Server-WSID")); - } - } -} diff --git a/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketRequestHandler.java b/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketRequestHandler.java index 31a805e..f781f90 100644 --- a/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketRequestHandler.java +++ b/src/main/java/org/javawebstack/httpserver/websocket/InternalWebSocketRequestHandler.java @@ -1,15 +1,17 @@ package org.javawebstack.httpserver.websocket; -import org.eclipse.jetty.server.Request; import org.javawebstack.httpserver.Exchange; +import org.javawebstack.httpserver.adapter.IHTTPSocket; import org.javawebstack.httpserver.handler.RequestHandler; import org.javawebstack.httpserver.handler.WebSocketHandler; +import org.javawebstack.httpserver.util.websocket.WebSocketFrame; +import org.javawebstack.httpserver.util.websocket.WebSocketUtil; -import javax.servlet.ServletException; import java.io.IOException; -import java.util.UUID; +import java.nio.charset.StandardCharsets; public class InternalWebSocketRequestHandler implements RequestHandler { + private final WebSocketHandler handler; public InternalWebSocketRequestHandler(WebSocketHandler handler) { @@ -17,14 +19,41 @@ public InternalWebSocketRequestHandler(WebSocketHandler handler) { } public Object handle(Exchange exchange) { - String id = UUID.randomUUID().toString(); - exchange.rawResponse().setHeader("X-Server-WSID", id); - InternalWebSocketAdapter.webSockets.put(id, new WebSocket(exchange, handler)); + IHTTPSocket socket = exchange.socket(); try { - exchange.getServer().getInternalWebSocketHandler().handle(exchange.getPath(), (Request) exchange.rawRequest(), exchange.rawRequest(), exchange.rawResponse()); - } catch (IOException | ServletException ignored) { - ignored.printStackTrace(); - } + if(!WebSocketUtil.accept(socket, null)) + return null; + WebSocket webSocket = new WebSocket(exchange); + handler.onConnect(webSocket); + WebSocketFrame frame; + while (true) { + try { + frame = WebSocketFrame.read(socket.getInputStream()); + } catch (IOException ex) { + handler.onClose(webSocket, null, null); + socket.close(); + break; + } + if(frame.getOpcode() == WebSocketUtil.OP_CLOSE) { + WebSocketUtil.ClosePayload close = WebSocketUtil.parseClose(frame.getPayload()); + handler.onClose(webSocket, close.getCode(), close.getReason()); + socket.close(); + break; + } + if(frame.getOpcode() == WebSocketUtil.OP_PING) { + frame.setOpcode(WebSocketUtil.OP_PONG).setMaskKey(null).write(socket.getOutputStream()); + continue; + } + if(frame.getOpcode() == WebSocketUtil.OP_BINARY) { + handler.onMessage(webSocket, frame.getPayload()); + continue; + } + if(frame.getOpcode() == WebSocketUtil.OP_TEXT) { + handler.onMessage(webSocket, new String(frame.getPayload(), StandardCharsets.UTF_8)); + continue; + } + } + } catch (IOException ignored) {} return null; } } diff --git a/src/main/java/org/javawebstack/httpserver/websocket/WebSocket.java b/src/main/java/org/javawebstack/httpserver/websocket/WebSocket.java index 0fc7d6e..014b0f2 100644 --- a/src/main/java/org/javawebstack/httpserver/websocket/WebSocket.java +++ b/src/main/java/org/javawebstack/httpserver/websocket/WebSocket.java @@ -1,58 +1,44 @@ package org.javawebstack.httpserver.websocket; -import org.eclipse.jetty.websocket.api.Session; import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.handler.WebSocketHandler; +import org.javawebstack.httpserver.util.websocket.WebSocketUtil; import java.io.IOException; -import java.nio.ByteBuffer; public class WebSocket { private final Exchange exchange; - private final WebSocketHandler handler; - private Session session; - public WebSocket(Exchange exchange, WebSocketHandler handler) { + public WebSocket(Exchange exchange) { this.exchange = exchange; - this.handler = handler; } public Exchange getExchange() { return exchange; } - public WebSocketHandler getHandler() { - return handler; - } - - void setSession(Session session) { - this.session = session; - } - public void close() { - session.close(); + close(null, null); } - public void close(int code, String reason) { - session.close(code, reason); + public void close(Integer code, String reason) { + try { + WebSocketUtil.close(exchange.socket(), code, reason); + } catch (IOException ignored) {} } public void send(String message) { try { - session.getRemote().sendString(message); + WebSocketUtil.send(exchange.socket(), message); } catch (IOException ignored) { } } public void send(byte[] message) { try { - session.getRemote().sendBytes(ByteBuffer.wrap(message)); + WebSocketUtil.send(exchange.socket(), message); } catch (IOException ignored) { } } - public Session getSession() { - return session; - } } From 313ac6705bd80648cdaa1dcecd4f780aa256cb91 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Sun, 5 Dec 2021 09:36:33 +0100 Subject: [PATCH 3/6] Fixed locale header --- src/main/java/org/javawebstack/httpserver/Exchange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/httpserver/Exchange.java b/src/main/java/org/javawebstack/httpserver/Exchange.java index 0fd04ef..2b717e3 100644 --- a/src/main/java/org/javawebstack/httpserver/Exchange.java +++ b/src/main/java/org/javawebstack/httpserver/Exchange.java @@ -181,7 +181,7 @@ public Exchange redirect(String url) { } public List locales() { - String locale = socket.getRequestHeader("locale"); + String locale = socket.getRequestHeader("accept-language"); if(locale == null) return new ArrayList<>(); return Stream.of(locale.split(" ?,")).map(s -> s.split(";")[0]).map(Locale::forLanguageTag).collect(Collectors.toList()); From bbe6c85fb8d874ec42b6c6062e9727b2d60b13f3 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Sun, 5 Dec 2021 10:03:10 +0100 Subject: [PATCH 4/6] Made jetty optional and upgraded junit --- pom.xml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index b54f0a3..8de625a 100644 --- a/pom.xml +++ b/pom.xml @@ -56,11 +56,7 @@ org.eclipse.jetty jetty-server 9.4.44.v20210927 - - - org.eclipse.jetty.websocket - websocket-server - 9.4.43.v20210629 + true org.reflections @@ -70,12 +66,12 @@ org.junit.jupiter junit-jupiter-api - 5.8.1 + 5.8.2 org.junit.jupiter junit-jupiter-engine - 5.8.1 + 5.8.2 test From 620c5da0882a854a94d047d4bfe457fa5c4d1ada Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 7 Dec 2021 16:29:05 +0100 Subject: [PATCH 5/6] Upgraded jetty from 9.44.x to 11.0.7 because the abstraction allowed it and there are reported issues with older versions --- pom.xml | 2 +- .../org/javawebstack/httpserver/HTTPServer.java | 1 + .../adapter/jetty/JettyHTTPSocket.java | 4 ++-- .../adapter/jetty/JettyHTTPSocketServer.java | 17 +++-------------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 8de625a..1c47f17 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ org.eclipse.jetty jetty-server - 9.4.44.v20210927 + 11.0.7 true diff --git a/src/main/java/org/javawebstack/httpserver/HTTPServer.java b/src/main/java/org/javawebstack/httpserver/HTTPServer.java index 04cefb7..cc4931e 100644 --- a/src/main/java/org/javawebstack/httpserver/HTTPServer.java +++ b/src/main/java/org/javawebstack/httpserver/HTTPServer.java @@ -407,4 +407,5 @@ public byte[] transformResponse(Exchange exchange, Object object) { public ExceptionHandler getExceptionHandler() { return exceptionHandler; } + } diff --git a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java index 9c959ce..7cc94dd 100644 --- a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocket.java @@ -1,11 +1,11 @@ package org.javawebstack.httpserver.adapter.jetty; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.javawebstack.httpserver.HTTPMethod; import org.javawebstack.httpserver.HTTPStatus; import org.javawebstack.httpserver.adapter.IHTTPSocket; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; diff --git a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java index c0f1def..3a4f0b1 100644 --- a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java @@ -1,5 +1,8 @@ package org.javawebstack.httpserver.adapter.jetty; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -7,11 +10,6 @@ import org.javawebstack.httpserver.adapter.IHTTPSocketHandler; import org.javawebstack.httpserver.adapter.IHTTPSocketServer; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.WebConnection; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -68,13 +66,4 @@ public void setHandler(IHTTPSocketHandler handler) { this.handler = handler; } - private static class DummyUpgradeHandler implements HttpUpgradeHandler { - public void init(WebConnection webConnection) { - - } - public void destroy() { - - } - } - } From 11d4a67477062b71ec0c0eb05755bc12d3bfcf26 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 7 Dec 2021 16:37:39 +0100 Subject: [PATCH 6/6] Added a check for websockets and switched back to jetty as the default implementation for until the own implementation is fully stable. --- pom.xml | 2 +- src/main/java/org/javawebstack/httpserver/HTTPServer.java | 5 ++++- .../javawebstack/httpserver/adapter/IHTTPSocketServer.java | 1 + .../httpserver/adapter/jetty/JettyHTTPSocketServer.java | 4 ++++ .../httpserver/adapter/simple/SimpleHTTPSocketServer.java | 4 ++++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c47f17..c47b35c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ org.eclipse.jetty jetty-server 11.0.7 - true + org.reflections diff --git a/src/main/java/org/javawebstack/httpserver/HTTPServer.java b/src/main/java/org/javawebstack/httpserver/HTTPServer.java index cc4931e..4868de8 100644 --- a/src/main/java/org/javawebstack/httpserver/HTTPServer.java +++ b/src/main/java/org/javawebstack/httpserver/HTTPServer.java @@ -3,6 +3,7 @@ import org.javawebstack.abstractdata.AbstractMapper; import org.javawebstack.abstractdata.NamingPolicy; import org.javawebstack.httpserver.adapter.IHTTPSocketServer; +import org.javawebstack.httpserver.adapter.jetty.JettyHTTPSocketServer; import org.javawebstack.httpserver.adapter.simple.SimpleHTTPSocketServer; import org.javawebstack.httpserver.handler.*; import org.javawebstack.httpserver.router.DefaultRouteAutoInjector; @@ -45,7 +46,7 @@ public class HTTPServer implements RouteParamTransformerProvider { private Function, Object> controllerInitiator = this::defaultControllerInitiator; public HTTPServer() { - this(new SimpleHTTPSocketServer()); + this(new JettyHTTPSocketServer()); } public HTTPServer(IHTTPSocketServer server) { @@ -196,6 +197,8 @@ public HTTPServer afterAny(String pattern, AfterRequestHandler... handlers) { } public HTTPServer webSocket(String pattern, WebSocketHandler handler) { + if(!server.isWebSocketSupported()) + throw new UnsupportedOperationException(server.getClass().getName() + " does not support websockets!"); return route(HTTPMethod.WEBSOCKET, pattern, new InternalWebSocketRequestHandler(handler)); } diff --git a/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java index 1679146..08b88e7 100644 --- a/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java @@ -10,5 +10,6 @@ public interface IHTTPSocketServer { void stop(); void join(); void setHandler(IHTTPSocketHandler handler); + boolean isWebSocketSupported(); } diff --git a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java index 3a4f0b1..ed0ae8d 100644 --- a/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/jetty/JettyHTTPSocketServer.java @@ -66,4 +66,8 @@ public void setHandler(IHTTPSocketHandler handler) { this.handler = handler; } + public boolean isWebSocketSupported() { + return false; + } + } diff --git a/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java index 91aaeef..7b64f6a 100644 --- a/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java +++ b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java @@ -60,4 +60,8 @@ public void stop() { } catch (IOException e) {} } + public boolean isWebSocketSupported() { + return false; + } + }