diff --git a/README.md b/README.md
index eee3858..b7d54a3 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,6 @@ work-in-progress project though so it's not yet complete.
org.javawebstack
http-server
- 1.0.1
+ 1.0.2
```
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 65c9f91..ecab3e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
8
8
- 1.0.1-SNAPSHOT
+ 1.0.2-SNAPSHOT
org.javawebstack
@@ -32,6 +32,12 @@
JavaWebStack
https://javawebstack.org
+
+ Julian Gojani
+ julian@gojani.xyz
+ JavaWebStack
+ https://javawebstack.org
+
@@ -44,17 +50,7 @@
org.javawebstack
validator
- 1.0.0
-
-
- org.eclipse.jetty
- jetty-server
- 9.4.44.v20210927
-
-
- org.eclipse.jetty.websocket
- websocket-server
- 9.4.43.v20210629
+ 1.0.1
org.reflections
@@ -64,12 +60,24 @@
org.junit.jupiter
junit-jupiter-api
- 5.8.1
+ 5.9.0
+
+
+ io.undertow
+ undertow-core
+ 2.2.19.Final
+
+
+
+ org.jboss.xnio
+ xnio-api
+ 3.8.8.Final
+
org.junit.jupiter
junit-jupiter-engine
- 5.8.1
+ 5.9.0
test
diff --git a/src/main/java/org/javawebstack/httpserver/Exchange.java b/src/main/java/org/javawebstack/httpserver/Exchange.java
index 158861c..c29e568 100644
--- a/src/main/java/org/javawebstack/httpserver/Exchange.java
+++ b/src/main/java/org/javawebstack/httpserver/Exchange.java
@@ -1,24 +1,21 @@
package org.javawebstack.httpserver;
-import org.javawebstack.abstractdata.AbstractElement;
-import org.javawebstack.abstractdata.AbstractMapper;
-import org.javawebstack.abstractdata.AbstractNull;
-import org.javawebstack.abstractdata.AbstractObject;
-import org.javawebstack.httpserver.helper.HttpMethod;
-import org.javawebstack.httpserver.helper.MimeType;
+import org.javawebstack.abstractdata.*;
+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.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class Exchange {
@@ -29,22 +26,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 = getRequestMethodFromSocket(socket);
+ this.queryParameters = AbstractElement.fromFormData(socket.getRequestQuery()).object();
}
public T body(Class clazz) {
@@ -60,30 +53,24 @@ public T body(Class clazz) {
if (body.length() == 0)
body = "{}";
- String contentType = getContentType().toLowerCase();
-
- if (contentType.contains(";")) {
- contentType = contentType.split(";")[0].trim();
- }
-
- MimeType type = MimeType.byMimeType(contentType);
+ MimeType type = getMimeType();
if (type == null)
type = MimeType.JSON;
- AbstractElement request = AbstractNull.INSTANCE;
+ AbstractElement request = null;
+ boolean arrayLike = clazz.isArray() || Collection.class.isAssignableFrom(clazz);
switch (type) {
case JSON:
request = AbstractElement.fromJson(body);
break;
case YAML:
- request = AbstractElement.fromYaml(body, !(clazz.isArray() || Collection.class.isAssignableFrom(clazz)));
+ request = AbstractElement.fromYaml(body, !arrayLike);
break;
case X_WWW_FORM_URLENCODED:
request = AbstractElement.fromFormData(body);
break;
- default:
- request = new AbstractObject();
- break;
}
+ if(request == null || request.isNull())
+ request = arrayLike ? new AbstractArray() : new AbstractObject();
ValidationResult result = Validator.getValidator(clazz).validate(new ValidationContext().attrib("exchange", this), request);
if (!result.isValid())
throw new ValidationException(result);
@@ -94,22 +81,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) {
@@ -129,8 +117,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;
@@ -138,8 +126,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;
@@ -156,49 +144,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("accept-language");
+ 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;
}
@@ -212,15 +201,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);
+ return header("content-type", contentType);
}
- public HttpServletRequest rawRequest() {
- return request;
- }
-
- public HttpServletResponse rawResponse() {
- return response;
+ public IHTTPSocket socket() {
+ return socket;
}
public T attrib(String key) {
@@ -258,7 +243,7 @@ public T query(String name, Class type, T defaultValue) {
}
public String remoteAddr() {
- return request.getRemoteAddr();
+ return socket.getRemoteAddress();
}
public Map getPathVariables() {
@@ -302,24 +287,23 @@ 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;
+ private HTTPMethod getRequestMethodFromSocket(IHTTPSocket socket) {
+ if ("websocket".equalsIgnoreCase(socket.getRequestHeader("upgrade")))
+ return HTTPMethod.WEBSOCKET;
+ if (server.isFormMethods() && (socket.getRequestMethod() == HTTPMethod.GET || socket.getRequestMethod() == HTTPMethod.POST) && getMimeType() == MimeType.X_WWW_FORM_URLENCODED) {
+ String rawMethodOverride = getBodyPathElement("_method").string();
+ if (rawMethodOverride != null)
+ return HTTPMethod.valueOf(rawMethodOverride);
+ }
+ return socket.getRequestMethod();
}
+ public MimeType getMimeType() {
+ String contentType = getContentType().toLowerCase();
+ if (contentType.contains(";")) {
+ contentType = contentType.split(";")[0].trim();
+ }
- public Exchange enableMultipart(String location, long maxFileSize, int fileSizeThreshold) {
- request.setAttribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement(location, maxFileSize, -1L, fileSizeThreshold));
- return this;
+ return MimeType.byMimeType(contentType);
}
}
diff --git a/src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java
similarity index 59%
rename from src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java
rename to src/main/java/org/javawebstack/httpserver/HTTPMethod.java
index cb60e36..6bc93f4 100644
--- a/src/main/java/org/javawebstack/httpserver/helper/HttpMethod.java
+++ b/src/main/java/org/javawebstack/httpserver/HTTPMethod.java
@@ -1,6 +1,7 @@
-package org.javawebstack.httpserver.helper;
+package org.javawebstack.httpserver;
+
+public enum HTTPMethod {
-public enum HttpMethod {
GET,
POST,
PUT,
@@ -8,8 +9,8 @@ public enum HttpMethod {
DELETE,
HEAD,
OPTIONS,
- CONNECT,
- MOVE,
TRACE,
+ CONNECT,
WEBSOCKET
+
}
diff --git a/src/main/java/org/javawebstack/httpserver/HTTPServer.java b/src/main/java/org/javawebstack/httpserver/HTTPServer.java
index a31fc67..c3ee9b8 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.undertow.UndertowHTTPSocketServer;
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,16 +15,13 @@
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.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -44,20 +36,30 @@ 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;
+ private boolean formMethods = true;
public HTTPServer() {
+ this(new UndertowHTTPSocketServer());
+ }
+
+ public HTTPServer(IHTTPSocketServer server) {
+ this.server = server;
routeParamTransformers.add(DefaultRouteParamTransformer.INSTANCE);
routeAutoInjectors.add(DefaultRouteAutoInjector.INSTANCE);
}
+ public HTTPServer maxThreads(int maxThreads) {
+ this.server.setMaxThreads(maxThreads);
+ return this;
+ }
+
public HTTPServer logger(Logger logger) {
this.logger = logger;
return this;
@@ -87,43 +89,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) {
@@ -147,60 +149,62 @@ 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));
+ if(!server.isWebSocketSupported())
+ throw new UnsupportedOperationException(server.getClass().getName() + " does not support websockets!");
+ return route(HTTPMethod.WEBSOCKET, pattern, new InternalWebSocketRequestHandler(handler));
}
public HTTPServer middleware(String name, RequestHandler handler) {
@@ -233,19 +237,31 @@ public HTTPServer exceptionHandler(ExceptionHandler handler) {
return this;
}
+ private Object defaultControllerInitiator (Class> clazz) {
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public HTTPServer controllerInitiator (Function, Object> initiator) {
+ controllerInitiator = initiator;
+ return this;
+ }
+
public HTTPServer controller(Class> parentClass, Package p) {
return controller("", parentClass, p);
}
public HTTPServer controller(String globalPrefix, Class> parentClass, Package p) {
Reflections reflections = new Reflections(p.getName());
- reflections.getSubTypesOf(parentClass).forEach(c -> {
- try {
- Object controller = c.newInstance();
- controller(globalPrefix, controller);
- } catch (InstantiationException | IllegalAccessException e) {
- }
- });
+ reflections.getSubTypesOf(parentClass)
+ .stream()
+ .map(controllerInitiator)
+ .forEach(c -> controller(globalPrefix, c));
return this;
}
@@ -259,24 +275,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);
}
@@ -284,11 +291,7 @@ public void configure(WebSocketServletFactory webSocketServletFactory) {
}
public void join() {
- try {
- server.join();
- } catch (InterruptedException ex) {
- throw new RuntimeException(ex);
- }
+ server.join();
}
public void stop() {
@@ -337,7 +340,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;
}
@@ -364,7 +367,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;
@@ -386,7 +389,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);
}
@@ -410,17 +412,16 @@ 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 ExceptionHandler getExceptionHandler() {
+ return exceptionHandler;
}
- public org.eclipse.jetty.websocket.server.WebSocketHandler getInternalWebSocketHandler() {
- return webSocketHandler;
+ public boolean isFormMethods() {
+ return formMethods;
}
- public ExceptionHandler getExceptionHandler() {
- return exceptionHandler;
+ public HTTPServer disableFormMethods() {
+ formMethods = false;
+ return this;
}
}
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..1b03a38
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/HTTPStatus.java
@@ -0,0 +1,94 @@
+package org.javawebstack.httpserver;
+
+public enum HTTPStatus {
+
+ CONTINUE(100, "Continue"),
+ SWITCHING_PROTOCOLS(101, "Switching Protocols"),
+ PROCESSING(102, "Processing"),
+
+ 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/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..28dd1dd
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/IHTTPSocketServer.java
@@ -0,0 +1,16 @@
+package org.javawebstack.httpserver.adapter;
+
+import java.io.IOException;
+
+public interface IHTTPSocketServer {
+
+ void setPort(int port);
+ int getPort();
+ void setMaxThreads(int maxThreads);
+ void start() throws IOException;
+ void stop();
+ void join();
+ void setHandler(IHTTPSocketHandler handler);
+ boolean isWebSocketSupported();
+
+}
diff --git a/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java
new file mode 100644
index 0000000..6d9ad27
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocket.java
@@ -0,0 +1,195 @@
+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;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class SimpleHTTPSocket implements IHTTPSocket {
+
+ 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 SimpleHTTPSocket(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 String getRemoteAddress() {
+ return socket.getInetAddress().getHostAddress();
+ }
+
+ public SimpleHTTPSocket setResponseStatus(HTTPStatus status) {
+ return setResponseStatus(status.getStatus(), status.getMessage());
+ }
+
+ public SimpleHTTPSocket setResponseStatus(int status) {
+ HTTPStatus s = HTTPStatus.byStatus(status);
+ return setResponseStatus(status, s != null ? s.getMessage() : "Unknown");
+ }
+
+ public SimpleHTTPSocket setResponseStatus(int status, String message) {
+ this.responseStatus = status;
+ this.responseStatusMessage = message;
+ return this;
+ }
+
+ public SimpleHTTPSocket setResponseHeader(String name, String value) {
+ responseHeaders.put(name.toLowerCase(Locale.ROOT), Arrays.asList(value));
+ return this;
+ }
+
+ public SimpleHTTPSocket 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 {
+ if(headersSent)
+ return;
+ 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 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 List getRequestHeaders(String name) {
+ return requestHeaders.getOrDefault(name.toLowerCase(Locale.ROOT), Collections.emptyList());
+ }
+
+ 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/adapter/simple/SimpleHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java
new file mode 100644
index 0000000..1341629
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/simple/SimpleHTTPSocketServer.java
@@ -0,0 +1,82 @@
+package org.javawebstack.httpserver.adapter.simple;
+
+import org.javawebstack.httpserver.adapter.IHTTPSocketHandler;
+import org.javawebstack.httpserver.adapter.IHTTPSocketServer;
+import org.javawebstack.httpserver.util.websocket.WebSocketUtil;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.*;
+
+public class SimpleHTTPSocketServer implements IHTTPSocketServer {
+
+ private final Thread schedulerThread;
+ private ExecutorService executorService;
+ private ServerSocket serverSocket;
+ private int port = 80;
+ private int maxThreads = 64;
+ private IHTTPSocketHandler handler;
+
+ public SimpleHTTPSocketServer() {
+ this.schedulerThread = new Thread(() -> {
+ while (!serverSocket.isClosed()) {
+ try {
+ Socket socket = serverSocket.accept();
+ SimpleHTTPSocket httpSocket = new SimpleHTTPSocket(socket);
+ executorService.execute(() -> {
+ if(httpSocket.getRequestHeaderNames().contains("sec-websocket-key")) {
+ try {
+ if(!WebSocketUtil.accept(httpSocket, null))
+ return;
+ } catch (IOException e) {
+ return;
+ }
+ }
+ 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 setMaxThreads(int maxThreads) {
+ this.maxThreads = maxThreads;
+ }
+
+ public void start() throws IOException {
+ this.serverSocket = new ServerSocket(port);
+ this.executorService = new ThreadPoolExecutor(1, maxThreads, 60L, TimeUnit.SECONDS, new SynchronousQueue());
+ 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) {}
+ }
+
+ public boolean isWebSocketSupported() {
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSinkOutputStream.java b/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSinkOutputStream.java
new file mode 100644
index 0000000..56fcb2b
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSinkOutputStream.java
@@ -0,0 +1,29 @@
+package org.javawebstack.httpserver.adapter.undertow;
+
+import org.xnio.channels.StreamSinkChannel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+public class StreamSinkOutputStream extends OutputStream {
+
+ private final StreamSinkChannel sink;
+ private final ByteBuffer byteBuffer = ByteBuffer.allocate(1);
+
+ public StreamSinkOutputStream(StreamSinkChannel sink) {
+ this.sink = sink;
+ }
+
+ public void write(int i) throws IOException {
+ byteBuffer.position(0);
+ byteBuffer.put((byte) i);
+ byteBuffer.position(0);
+ sink.write(byteBuffer);
+ }
+
+ public void close() throws IOException {
+ sink.close();
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSourceInputStream.java b/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSourceInputStream.java
new file mode 100644
index 0000000..79ffba5
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/undertow/StreamSourceInputStream.java
@@ -0,0 +1,29 @@
+package org.javawebstack.httpserver.adapter.undertow;
+
+import org.xnio.channels.StreamSourceChannel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class StreamSourceInputStream extends InputStream {
+
+ private final StreamSourceChannel source;
+ private final ByteBuffer byteBuffer = ByteBuffer.allocate(1);
+
+ public StreamSourceInputStream(StreamSourceChannel source) {
+ this.source = source;
+ }
+
+ public synchronized int read() throws IOException {
+ byteBuffer.position(0);
+ int r;
+ while ((r = source.read(byteBuffer)) == 0)
+ Thread.yield();
+ if(r == -1)
+ return -1;
+ byteBuffer.position(0);
+ return byteBuffer.get();
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocket.java b/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocket.java
new file mode 100644
index 0000000..356b513
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocket.java
@@ -0,0 +1,132 @@
+package org.javawebstack.httpserver.adapter.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HeaderValues;
+import io.undertow.util.HttpString;
+import org.javawebstack.httpserver.HTTPMethod;
+import org.javawebstack.httpserver.HTTPStatus;
+import org.javawebstack.httpserver.adapter.IHTTPSocket;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class UndertowHTTPSocket implements IHTTPSocket {
+
+ private final HttpServerExchange exchange;
+ private final InputStream inputStream;
+ private final OutputStream outputStream;
+ private boolean closed;
+
+ public UndertowHTTPSocket(HttpServerExchange exchange, InputStream inputStream, OutputStream outputStream) {
+ this.exchange = exchange;
+ InputStream is = inputStream == null ? exchange.getInputStream() : inputStream;
+ this.inputStream = new InputStream() {
+ public int read() throws IOException {
+ return is.read();
+ }
+ public int available() throws IOException {
+ int a = is.available();
+ if(a == 0 && !exchange.isRequestComplete())
+ a = 1;
+ return a;
+ }
+ };
+ this.outputStream = outputStream == null ? exchange.getOutputStream() : outputStream;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return inputStream;
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ return outputStream;
+ }
+
+ public void close() throws IOException {
+ if(closed)
+ return;
+ closed = true;
+ exchange.getOutputStream().close();
+ }
+
+ public boolean isClosed() {
+ return exchange.isComplete();
+ }
+
+ public IHTTPSocket setResponseStatus(int status, String message) {
+ if(!exchange.isResponseStarted())
+ exchange.setStatusCode(status);
+ return this;
+ }
+
+ public IHTTPSocket setResponseHeader(String name, String value) {
+ exchange.getResponseHeaders().put(new HttpString(name), value);
+ return this;
+ }
+
+ public IHTTPSocket addResponseHeader(String name, String value) {
+ exchange.getResponseHeaders().add(new HttpString(name), value);
+ return this;
+ }
+
+ public HTTPMethod getRequestMethod() {
+ return HTTPMethod.valueOf(exchange.getRequestMethod().toString());
+ }
+
+ public String getRequestPath() {
+ return exchange.getRequestPath();
+ }
+
+ public String getRequestQuery() {
+ return exchange.getQueryString();
+ }
+
+ public String getRequestVersion() {
+ return exchange.getProtocol().toString();
+ }
+
+ public Set getRequestHeaderNames() {
+ return exchange.getRequestHeaders().getHeaderNames().stream().map(HttpString::toString).collect(Collectors.toSet());
+ }
+
+ public String getRequestHeader(String name) {
+ HeaderValues values = exchange.getRequestHeaders().get(name);
+ if(values == null)
+ return null;
+ return values.getFirst();
+ }
+
+ public List getRequestHeaders(String name) {
+ HeaderValues values = exchange.getRequestHeaders().get(name);
+ if(values == null)
+ return Collections.emptyList();
+ return new ArrayList<>(values);
+ }
+
+ public int getResponseStatus() {
+ return exchange.getStatusCode();
+ }
+
+ public String getResponseStatusMessage() {
+ HTTPStatus status = HTTPStatus.byStatus(getResponseStatus());
+ if(status == null)
+ return null;
+ return status.getMessage();
+ }
+
+ public void writeHeaders() throws IOException {
+ exchange.getOutputStream().write(new byte[0]);
+ }
+
+ public String getRemoteAddress() {
+ return exchange.getSourceAddress().getAddress().getHostAddress();
+ }
+
+ public HttpServerExchange getExchange() {
+ return exchange;
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocketServer.java b/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocketServer.java
new file mode 100644
index 0000000..178d0d2
--- /dev/null
+++ b/src/main/java/org/javawebstack/httpserver/adapter/undertow/UndertowHTTPSocketServer.java
@@ -0,0 +1,80 @@
+package org.javawebstack.httpserver.adapter.undertow;
+
+import io.undertow.Undertow;
+import io.undertow.server.handlers.BlockingHandler;
+import io.undertow.websockets.core.WebSocketVersion;
+import org.javawebstack.httpserver.adapter.IHTTPSocketHandler;
+import org.javawebstack.httpserver.adapter.IHTTPSocketServer;
+import org.javawebstack.httpserver.util.websocket.WebSocketUtil;
+import org.xnio.Options;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.*;
+
+public class UndertowHTTPSocketServer implements IHTTPSocketServer {
+
+ private int port = 80;
+ private int maxThreads = 64;
+ private Undertow server;
+ private IHTTPSocketHandler handler;
+ private ExecutorService executorService;
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void start() throws IOException {
+ executorService = new ThreadPoolExecutor(1, maxThreads, 60L, TimeUnit.SECONDS, new SynchronousQueue());
+ server = Undertow.builder()
+ .addHttpListener(port, "0.0.0.0")
+ .setServerOption(Options.KEEP_ALIVE, true)
+ .setHandler(new BlockingHandler(httpServerExchange -> {
+ httpServerExchange.setDispatchExecutor(executorService);
+ if(httpServerExchange.getRequestHeaders().contains("sec-websocket-key")) {
+ httpServerExchange.upgradeChannel((streamConnection, httpServerExchange1) -> {
+ InputStream inputStream = new StreamSourceInputStream(streamConnection.getSourceChannel());
+ OutputStream outputStream = new StreamSinkOutputStream(streamConnection.getSinkChannel());
+ handler.handle(new UndertowHTTPSocket(httpServerExchange1, inputStream, outputStream));
+ });
+ httpServerExchange.putAttachment(WebSocketVersion.ATTACHMENT_KEY, WebSocketVersion.V13);
+ if(!WebSocketUtil.accept(new UndertowHTTPSocket(httpServerExchange, null, null), null))
+ return;
+ httpServerExchange.endExchange();
+ } else {
+ handler.handle(new UndertowHTTPSocket(httpServerExchange, null, null));
+ }
+ }))
+ .build();
+ server.start();
+ }
+
+ public void stop() {
+ executorService.shutdown();
+ server.stop();
+ }
+
+ public void join() {
+ try {
+ server.getWorker().awaitTermination();
+ } catch (InterruptedException e) {}
+ }
+
+ public void setHandler(IHTTPSocketHandler handler) {
+ this.handler = handler;
+ }
+
+ public void setMaxThreads(int maxThreads) {
+ this.maxThreads = maxThreads;
+ }
+
+ public boolean isWebSocketSupported() {
+ return true;
+ }
+
+}
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/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
return exchange;
if (WebSocket.class.isAssignableFrom(type) && extraArgs.containsKey("websocket"))
return extraArgs.get("websocket");
- if (type.equals(HttpMethod.class))
+ if (type.equals(HTTPMethod.class))
return exchange.getMethod();
return null;
}
diff --git a/src/main/java/org/javawebstack/httpserver/router/Route.java b/src/main/java/org/javawebstack/httpserver/router/Route.java
index af1ed3b..7adaae7 100644
--- a/src/main/java/org/javawebstack/httpserver/router/Route.java
+++ b/src/main/java/org/javawebstack/httpserver/router/Route.java
@@ -1,9 +1,9 @@
package org.javawebstack.httpserver.router;
import org.javawebstack.httpserver.Exchange;
+import org.javawebstack.httpserver.HTTPMethod;
import org.javawebstack.httpserver.handler.AfterRequestHandler;
import org.javawebstack.httpserver.handler.RequestHandler;
-import org.javawebstack.httpserver.helper.HttpMethod;
import org.javawebstack.httpserver.transformer.route.RouteParamTransformerProvider;
import java.util.HashMap;
@@ -15,17 +15,17 @@
public class Route {
private final RouteParamTransformerProvider routeParamTransformerProvider;
- private final HttpMethod method;
+ private final HTTPMethod method;
private final Pattern pattern;
private final Map 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..e8eb2eb 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,16 +33,16 @@ 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;
}
}
Map websocketHandlers = new HashMap<>();
- for (Method method : controller.getClass().getDeclaredMethods()) {
+ for (Method method : getMethodsRecursive(controller.getClass())) {
List binds = new ArrayList<>();
With methodWith = getAnnotations(With.class, method).stream().findFirst().orElse(null);
List middlewares = new ArrayList<>();
@@ -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);
@@ -183,6 +183,13 @@ private static T getAnnotation(Class type, Method meth
return annotations.length == 0 ? null : annotations[0];
}
+ private static List getMethodsRecursive(Class> type) {
+ List methods = new ArrayList<>(Arrays.asList(type.getDeclaredMethods()));
+ if (type.getSuperclass() != null && type.getSuperclass() != Object.class)
+ methods.addAll(getMethodsRecursive(type.getSuperclass()));
+ return methods;
+ }
+
private static class BindMapper {
private final HTTPServer server;
@@ -279,7 +286,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/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..c0948d4
--- /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] = safeRead(stream);
+ frame.maskKey[1] = safeRead(stream);
+ frame.maskKey[2] = safeRead(stream);
+ frame.maskKey[3] = 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..99831de 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,39 @@ 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();
- }
+ 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;
- }
}