From 67f7a18f798518dd6faaa438f6f71c5bad1d0697 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Fri, 7 Mar 2014 16:30:19 +0100 Subject: [PATCH 01/10] Change simpleframework by jetty just fix compile error Tests run: 261, Failures: 57, Errors: 3, Skipped: 0 --- pom.xml | 11 +- .../java/net/codestory/http/WebServer.java | 103 +++++++++++------- .../codestory/http/convert/TypeConvert.java | 4 +- .../filters/twitter/TwitterAuthFilter.java | 13 +-- .../net/codestory/http/internal/Context.java | 46 ++++---- .../net/codestory/http/payload/Payload.java | 32 +++--- .../codestory/http/routes/RouteWrapper.java | 2 +- .../codestory/http/internal/ContextTest.java | 16 ++- .../codestory/http/payload/PayloadTest.java | 33 +++--- 9 files changed, 154 insertions(+), 106 deletions(-) diff --git a/pom.xml b/pom.xml index fdbf129..4f69dd1 100644 --- a/pom.xml +++ b/pom.xml @@ -197,9 +197,14 @@ - org.simpleframework - simple - 5.1.6 + org.eclipse.jetty + jetty-server + 9.1.3.v20140225 + + + org.eclipse.jetty + jetty-servlet + 9.1.3.v20140225 com.github.sommeri diff --git a/src/main/java/net/codestory/http/WebServer.java b/src/main/java/net/codestory/http/WebServer.java index 6a02407..3f34979 100644 --- a/src/main/java/net/codestory/http/WebServer.java +++ b/src/main/java/net/codestory/http/WebServer.java @@ -29,19 +29,27 @@ import net.codestory.http.routes.*; import net.codestory.http.ssl.*; -import org.simpleframework.http.*; -import org.simpleframework.http.core.*; -import org.simpleframework.transport.*; -import org.simpleframework.transport.connect.*; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; import org.slf4j.*; import javax.net.ssl.*; - -public class WebServer { +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class WebServer implements Filter { private final static Logger LOG = LoggerFactory.getLogger(WebServer.class); - private final Server server; - private final SocketConnection connection; + private Server server; private RoutesProvider routesProvider; private int port; @@ -51,12 +59,6 @@ public WebServer() { } public WebServer(Configuration configuration) { - try { - server = new ContainerServer(this::handle); - connection = new SocketConnection(server); - } catch (IOException e) { - throw new IllegalStateException("Unable to create http server", e); - } configure(configuration); } @@ -109,7 +111,14 @@ private WebServer startWithContext(int port, SSLContext context) { try { this.port = Env.INSTANCE.overriddenPort(port); - connection.connect(new InetSocketAddress(this.port), context); + server = new Server(this.port); + + ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); + servletHandler.addFilter(new FilterHolder(this), "/*", EnumSet.of(DispatcherType.REQUEST)); + + server.setHandler(servletHandler); + + server.start(); LOG.info("Server started on port {}", this.port); } catch (RuntimeException e) { @@ -135,33 +144,8 @@ public void reset() { public void stop() { try { server.stop(); - } catch (IOException e) { - throw new IllegalStateException("Unable to stop the web server", e); - } - } - - void handle(Request request, Response response) { - Context context = null; - - try { - RouteCollection routes = routesProvider.get(); - context = new Context(request, response, routes.getIocAdapter()); - - applyRoutes(routes, context); } catch (Exception e) { - if (context == null) { - // Didn't manage to initialize a full context - // because the routes failed to load - // - context = new Context(request, response, null); - } - handleServerError(context, e); - } finally { - try { - response.close(); - } catch (IOException e) { - // Ignore - } + throw new IllegalStateException("Unable to stop the web server", e); } } @@ -198,4 +182,41 @@ protected Payload errorPage(Payload payload, Exception e) { Exception shownError = Env.INSTANCE.prodMode() ? null : e; return new ErrorPage(payload, shownError).payload(); } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // TODO, call spec class no init routes of user. + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + Context context = null; + + try { + RouteCollection routes = routesProvider.get(); + context = new Context((HttpServletRequest)request, (HttpServletResponse)response, routes.getIocAdapter()); + + applyRoutes(routes, context); + } catch (Exception e) { + if (context == null) { + // Didn't manage to initialize a full context + // because the routes failed to load + // + context = new Context((HttpServletRequest)request, (HttpServletResponse)response, null); + } + handleServerError(context, e); + } finally { + try { + response.getOutputStream().close(); + } catch (IOException e) { + // Ignore + } + } + } + + @Override + public void destroy() { + + } } diff --git a/src/main/java/net/codestory/http/convert/TypeConvert.java b/src/main/java/net/codestory/http/convert/TypeConvert.java index 2fe425a..7588acf 100644 --- a/src/main/java/net/codestory/http/convert/TypeConvert.java +++ b/src/main/java/net/codestory/http/convert/TypeConvert.java @@ -16,6 +16,7 @@ package net.codestory.http.convert; import java.io.*; +import java.nio.charset.Charset; import java.util.*; import net.codestory.http.internal.*; @@ -23,6 +24,7 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; +import net.codestory.http.io.InputStreams; public class TypeConvert { private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() @@ -71,7 +73,7 @@ public static T convert(Context context, Class type) { String json; try { - json = context.request().getContent(); + json = InputStreams.readString(context.request().getInputStream(), Charset.forName("UTF-8")); } catch (IOException e) { throw new IllegalArgumentException("Unable read request content", e); } diff --git a/src/main/java/net/codestory/http/filters/twitter/TwitterAuthFilter.java b/src/main/java/net/codestory/http/filters/twitter/TwitterAuthFilter.java index a7e0721..5d61cc8 100644 --- a/src/main/java/net/codestory/http/filters/twitter/TwitterAuthFilter.java +++ b/src/main/java/net/codestory/http/filters/twitter/TwitterAuthFilter.java @@ -22,7 +22,6 @@ import net.codestory.http.internal.*; import net.codestory.http.payload.*; -import org.simpleframework.http.*; import twitter4j.*; import twitter4j.conf.*; @@ -70,16 +69,16 @@ public Payload apply(String uri, Context context, PayloadSupplier nextFilter) th } return Payload.seeOther("/") - .withCookie(new Cookie("userId", user.id.toString(), "/", true)) - .withCookie(new Cookie("screenName", user.screenName, "/", true)) - .withCookie(new Cookie("userPhoto", user.imageUrl, "/", true)); + .withCookie("userId", user.id.toString()) + .withCookie("screenName", user.screenName) + .withCookie("userPhoto", user.imageUrl); } if (uri.equals(uriPrefix + "logout")) { return Payload.seeOther("/") - .withCookie(new Cookie("userId", "", "/", false)) - .withCookie(new Cookie("screenName", "", "/", false)) - .withCookie(new Cookie("userPhoto", "", "/", false)); + .withCookie("userId", "") + .withCookie("screenName", "") + .withCookie("userPhoto", ""); } String userId = context.cookieValue("userId"); diff --git a/src/main/java/net/codestory/http/internal/Context.java b/src/main/java/net/codestory/http/internal/Context.java index 30dc97b..567a737 100644 --- a/src/main/java/net/codestory/http/internal/Context.java +++ b/src/main/java/net/codestory/http/internal/Context.java @@ -19,33 +19,36 @@ import java.io.*; import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import net.codestory.http.convert.*; import net.codestory.http.injection.*; import net.codestory.http.io.*; -import org.simpleframework.http.*; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + public class Context { - private final Request request; - private final Response response; + private final HttpServletRequest request; + private final HttpServletResponse response; private final IocAdapter iocAdapter; - private final Query query; private String currentUser; - public Context(Request request, Response response, IocAdapter iocAdapter) { + public Context(HttpServletRequest request, HttpServletResponse response, IocAdapter iocAdapter) { this.request = request; this.response = response; this.iocAdapter = iocAdapter; - this.query = request.getQuery(); } public String uri() { - return request.getPath().getPath(); + return request.getPathInfo(); } public Cookie cookie(String name) { - return request.getCookie(name); + return Arrays.stream(request.getCookies()).filter(cookie -> name.equals(cookie.getName())).findFirst().orElse(null); } public String cookieValue(String name) { @@ -86,35 +89,35 @@ public boolean cookieValue(String name, boolean defaultValue) { } public List cookies() { - return request.getCookies(); + return Arrays.asList(request.getCookies()); } public String get(String name) { - return query.get(name); + return request.getParameter(name); } public List getAll(String name) { - return query.getAll(name); + return Arrays.asList(request.getParameterValues(name)); } public int getInteger(String name) { - return query.getInteger(name); + return Integer.parseInt(request.getParameter(name)); } public float getFloat(String name) { - return query.getFloat(name); + return Float.parseFloat(request.getParameter(name)); } public boolean getBoolean(String name) { - return query.getBoolean(name); + return Boolean.parseBoolean(request.getParameter(name)); } public String getHeader(String name) { - return request.getValue(name); + return request.getHeader(name); } public List getHeaders(String name) { - return request.getValues(name); + return Collections.list(request.getHeaders(name)); } public String method() { @@ -122,19 +125,22 @@ public String method() { } public Map keyValues() { - return query; + return Collections.list(request.getParameterNames()).stream().collect(Collectors.toMap( + Function.identity(), + name -> request.getParameter(name) + )); } public String getClientAddress() { String forwarded = getHeader(X_FORWARDED_FOR); - return (forwarded != null) ? forwarded : request.getClientAddress().toString(); + return (forwarded != null) ? forwarded : request.getRemoteAddr(); } - public Request request() { + public HttpServletRequest request() { return request; } - public Response response() { + public HttpServletResponse response() { return response; } diff --git a/src/main/java/net/codestory/http/payload/Payload.java b/src/main/java/net/codestory/http/payload/Payload.java index 5b3b503..1fbeaa9 100644 --- a/src/main/java/net/codestory/http/payload/Payload.java +++ b/src/main/java/net/codestory/http/payload/Payload.java @@ -19,9 +19,9 @@ import static net.codestory.http.constants.Encodings.*; import static net.codestory.http.constants.Headers.*; import static net.codestory.http.constants.HttpStatus.NOT_FOUND; +import static net.codestory.http.constants.HttpStatus.NOT_MODIFIED; import static net.codestory.http.constants.Methods.*; import static net.codestory.http.io.Strings.*; -import static org.simpleframework.http.Status.NOT_MODIFIED; import java.io.*; import java.net.*; @@ -37,8 +37,11 @@ import net.codestory.http.misc.*; import net.codestory.http.templating.*; import net.codestory.http.types.*; +import org.eclipse.jetty.server.Response; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; -import org.simpleframework.http.*; public class Payload { private final String contentType; @@ -112,7 +115,9 @@ public Payload withCookie(String name, boolean value) { } public Payload withCookie(String name, String value) { - return withCookie(new Cookie(name, value, "/", true)); + Cookie cookie = new Cookie(name, value); + cookie.setDomain("/"); + return withCookie(cookie); } public Payload withCookie(String name, Object value) { @@ -195,7 +200,7 @@ public static Payload temporaryRedirect(URI uri) { } public static Payload notModified() { - return new Payload(HttpStatus.NOT_MODIFIED); + return new Payload(NOT_MODIFIED); } public static Payload unauthorized(String realm) { @@ -229,10 +234,11 @@ public boolean isBetter(Payload other) { } public void writeTo(Context context) throws IOException { - Response response = context.response(); + HttpServletResponse response = context.response(); - headers.entrySet().forEach(entry -> response.setValue(entry.getKey(), entry.getValue())); - cookies.forEach(cookie -> response.setCookie(cookie)); + headers.entrySet().forEach(entry -> + response.setHeader(entry.getKey(), entry.getValue())); + cookies.forEach(cookie -> response.addCookie(cookie)); long lastModified = getLastModified(); if (lastModified >= 0) { @@ -241,20 +247,20 @@ public void writeTo(Context context) throws IOException { response.setStatus(NOT_MODIFIED); return; } - response.setValue(LAST_MODIFIED, Dates.to_rfc_1123(lastModified)); + response.setHeader(LAST_MODIFIED, Dates.to_rfc_1123(lastModified)); } String uri = context.uri(); byte[] data = getData(uri, context); if (data == null) { - response.setStatus(Status.getStatus(code)); + response.setStatus(code); response.setContentLength(0); return; } String type = getContentType(uri); - response.setValue(CONTENT_TYPE, type); - response.setStatus(Status.getStatus(code)); + response.setHeader(CONTENT_TYPE, type); + response.setStatus(code); if (HEAD.equals(context.method()) || (code == 204) || (code == 304) || ((code >= 100) && (code < 200))) { return; @@ -266,11 +272,11 @@ public void writeTo(Context context) throws IOException { response.setStatus(NOT_MODIFIED); return; } - response.setValue(ETAG, etag); + response.setHeader(ETAG, etag); String acceptEncoding = context.getHeader(ACCEPT_ENCODING); if ((acceptEncoding != null) && acceptEncoding.contains(GZIP)) { - response.setValue(CONTENT_ENCODING, GZIP); + response.setHeader(CONTENT_ENCODING, GZIP); GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream()); gzip.write(data); diff --git a/src/main/java/net/codestory/http/routes/RouteWrapper.java b/src/main/java/net/codestory/http/routes/RouteWrapper.java index fcd4691..67425d9 100644 --- a/src/main/java/net/codestory/http/routes/RouteWrapper.java +++ b/src/main/java/net/codestory/http/routes/RouteWrapper.java @@ -42,7 +42,7 @@ public boolean matchMethod(String method) { @Override public Object body(Context context) { - String[] parameters = uriParser.params(context.uri(), context.request().getQuery()); + String[] parameters = uriParser.params(context.uri(), context.keyValues()); return route.body(context, parameters); } } diff --git a/src/test/java/net/codestory/http/internal/ContextTest.java b/src/test/java/net/codestory/http/internal/ContextTest.java index c009a0a..74653f7 100644 --- a/src/test/java/net/codestory/http/internal/ContextTest.java +++ b/src/test/java/net/codestory/http/internal/ContextTest.java @@ -20,12 +20,16 @@ import net.codestory.http.injection.*; +import org.assertj.core.util.Arrays; import org.junit.*; -import org.simpleframework.http.*; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; public class ContextTest { - Request request = mock(Request.class); - Response response = mock(Response.class); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); IocAdapter iocAdapter = mock(IocAdapter.class); Context context = new Context(request, response, iocAdapter); @@ -46,7 +50,7 @@ public void default_value() { @Test public void cookie_value() { - when(request.getCookie("name")).thenReturn(new Cookie("name", "value")); + when(request.getCookies()).thenReturn(Arrays.array(new Cookie("name", "value"))); String value = context.cookieValue("name", "default"); @@ -55,7 +59,7 @@ public void cookie_value() { @Test public void json_cookie_json_by_type() { - when(request.getCookie("name")).thenReturn(new Cookie("name", "{\"name\": \"Bob\", \"quantity\": 42}")); + when(request.getCookies()).thenReturn(Arrays.array(new Cookie("name", "{\"name\": \"Bob\", \"quantity\": 42}"))); Order order = context.cookieValue("name", Order.class); @@ -73,7 +77,7 @@ public void json_cookie_default_value() { @Test public void json_cookie() { - when(request.getCookie("name")).thenReturn(new Cookie("name", "{\"name\": \"Joe\", \"quantity\": 12}")); + when(request.getCookies()).thenReturn(Arrays.array(new Cookie("name", "{\"name\": \"Joe\", \"quantity\": 12}"))); Order order = context.cookieValue("name", new Order()); diff --git a/src/test/java/net/codestory/http/payload/PayloadTest.java b/src/test/java/net/codestory/http/payload/PayloadTest.java index 85b38cb..c757742 100644 --- a/src/test/java/net/codestory/http/payload/PayloadTest.java +++ b/src/test/java/net/codestory/http/payload/PayloadTest.java @@ -23,19 +23,24 @@ import java.nio.file.*; import java.util.*; +import net.codestory.http.constants.HttpStatus; import net.codestory.http.internal.*; import org.junit.*; -import org.simpleframework.http.*; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; public class PayloadTest { Context context = mock(Context.class); - Response response = mock(Response.class); + HttpServletResponse response = mock(HttpServletResponse.class); @Before public void setupContext() throws IOException { when(context.response()).thenReturn(response); - when(response.getOutputStream()).thenReturn(new ByteArrayOutputStream()); + when(response.getOutputStream()).thenReturn(mock(ServletOutputStream.class)); } @Test @@ -94,7 +99,7 @@ public void support_absent_optional() throws IOException { Payload payload = new Payload("text/plain", Optional.empty()); payload.writeTo(context); - verify(response).setStatus(Status.NOT_FOUND); + verify(response).setStatus(HttpStatus.NOT_FOUND); verify(response).setContentLength(0); verifyNoMoreInteractions(response); } @@ -104,8 +109,8 @@ public void redirect() throws IOException { Payload payload = Payload.seeOther("/url"); payload.writeTo(context); - verify(response).setValue("Location", "/url"); - verify(response).setStatus(Status.SEE_OTHER); + verify(response).setHeader("Location", "/url"); + verify(response).setStatus(HttpStatus.SEE_OTHER); verify(response).setContentLength(0); verifyNoMoreInteractions(response); } @@ -115,7 +120,7 @@ public void forbidden() throws IOException { Payload payload = Payload.forbidden(); payload.writeTo(context); - verify(response).setStatus(Status.FORBIDDEN); + verify(response).setStatus(HttpStatus.FORBIDDEN); verify(response).setContentLength(0); verifyNoMoreInteractions(response); } @@ -125,8 +130,8 @@ public void permanent_move() throws IOException { Payload payload = Payload.movedPermanently("/url"); payload.writeTo(context); - verify(response).setValue("Location", "/url"); - verify(response).setStatus(Status.MOVED_PERMANENTLY); + verify(response).setHeader("Location", "/url"); + verify(response).setStatus(HttpStatus.MOVED_PERMANENTLY); verify(response).setContentLength(0); verifyNoMoreInteractions(response); } @@ -136,7 +141,7 @@ public void last_modified() throws IOException { Payload payload = new Payload(Paths.get("hello.md")); payload.writeTo(context); - verify(response).setValue(eq("Last-Modified"), anyString()); + verify(response).setHeader(eq("Last-Modified"), anyString()); } @Test @@ -183,8 +188,8 @@ public void etag() throws IOException { Payload payload = new Payload("Hello"); payload.writeTo(context); - verify(response).setStatus(Status.OK); - verify(response).setValue("ETag", "8b1a9953c4611296a827abf8c47804d7"); + verify(response).setStatus(HttpStatus.OK); + verify(response).setHeader("ETag", "8b1a9953c4611296a827abf8c47804d7"); } @Test @@ -194,7 +199,7 @@ public void not_modified() throws IOException { Payload payload = new Payload("Hello"); payload.writeTo(context); - verify(response).setStatus(Status.NOT_MODIFIED); + verify(response).setStatus(HttpStatus.NOT_MODIFIED); } @Test @@ -204,7 +209,7 @@ public void head() throws IOException { Payload payload = new Payload("Hello"); payload.writeTo(context); - verify(response).setStatus(Status.OK); + verify(response).setStatus(HttpStatus.OK); verify(response, never()).setContentLength(anyInt()); verify(response, never()).getOutputStream(); } From 0f7c4f709473ce7b2108bf17c1c6b741ea70f3e2 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Fri, 7 Mar 2014 17:11:06 +0100 Subject: [PATCH 02/10] Fix error in cookies() and uri() Tests run: 261, Failures: 2, Errors: 0, Skipped: 0 --- src/main/java/net/codestory/http/internal/Context.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/codestory/http/internal/Context.java b/src/main/java/net/codestory/http/internal/Context.java index 567a737..300f2f2 100644 --- a/src/main/java/net/codestory/http/internal/Context.java +++ b/src/main/java/net/codestory/http/internal/Context.java @@ -44,11 +44,11 @@ public Context(HttpServletRequest request, HttpServletResponse response, IocAdap } public String uri() { - return request.getPathInfo(); + return request.getRequestURI(); } public Cookie cookie(String name) { - return Arrays.stream(request.getCookies()).filter(cookie -> name.equals(cookie.getName())).findFirst().orElse(null); + return cookies().stream().filter(cookie -> name.equals(cookie.getName())).findFirst().orElse(null); } public String cookieValue(String name) { @@ -89,6 +89,9 @@ public boolean cookieValue(String name, boolean defaultValue) { } public List cookies() { + if (request.getCookies() == null || request.getCookies().length == 0) { + return new ArrayList<>(); + } return Arrays.asList(request.getCookies()); } From fa91a147b12a16b26b258f543811ae38134fc5f2 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Fri, 7 Mar 2014 17:33:59 +0100 Subject: [PATCH 03/10] Add URI decode Tests run: 261, Failures: 1, Errors: 0, Skipped: 0 --- .../net/codestory/http/internal/Context.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/codestory/http/internal/Context.java b/src/main/java/net/codestory/http/internal/Context.java index 300f2f2..1515e22 100644 --- a/src/main/java/net/codestory/http/internal/Context.java +++ b/src/main/java/net/codestory/http/internal/Context.java @@ -18,6 +18,7 @@ import static net.codestory.http.constants.Headers.*; import java.io.*; +import java.net.URLDecoder; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -44,7 +45,7 @@ public Context(HttpServletRequest request, HttpServletResponse response, IocAdap } public String uri() { - return request.getRequestURI(); + return uriDecode(request.getRequestURI()); } public Cookie cookie(String name) { @@ -96,11 +97,11 @@ public List cookies() { } public String get(String name) { - return request.getParameter(name); + return uriDecode(request.getParameter(name)); } public List getAll(String name) { - return Arrays.asList(request.getParameterValues(name)); + return Arrays.asList(request.getParameterValues(name)).stream().map(this::uriDecode).collect(Collectors.toList()); } public int getInteger(String name) { @@ -127,10 +128,19 @@ public String method() { return request.getMethod(); } + private String uriDecode(String uri) { + try { + return URLDecoder.decode(uri, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 not supported on your system", e); + } + } + public Map keyValues() { return Collections.list(request.getParameterNames()).stream().collect(Collectors.toMap( Function.identity(), - name -> request.getParameter(name) + name -> uriDecode(request.getParameter(name)) )); } From c4e00bb9b4ed0d97680bdeb14fe165ab0eca9dcd Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 10:09:41 +0100 Subject: [PATCH 04/10] Fix last test. Relative path which go upper than root are handle by jetty himself with status 400. Tests run: 261, Failures: 0, Errors: 0, Skipped: 0 --- src/test/java/net/codestory/http/StaticPagesTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/codestory/http/StaticPagesTest.java b/src/test/java/net/codestory/http/StaticPagesTest.java index f529afe..89bbfc4 100644 --- a/src/test/java/net/codestory/http/StaticPagesTest.java +++ b/src/test/java/net/codestory/http/StaticPagesTest.java @@ -63,7 +63,9 @@ public void markdown() { @Test public void private_files() { - get("/../private.txt").produces(404); + // Status 400 is handle by jetty himself + get("/../private.txt").produces(400); + get("/_config.yaml").produces(404); get("/_layouts/layout.html").produces(404); get("/unknown").produces(404); From cd164c0aef6ba32fa70b7c84073105a59cc0bb69 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 11:04:56 +0100 Subject: [PATCH 05/10] Add SSL Support and a real test in SSLTest --- .../java/net/codestory/http/WebServer.java | 28 +++++++++++++++---- .../codestory/http/ssl/SSLContextFactory.java | 9 ++++-- .../java/net/codestory/http/ssl/SSLTest.java | 7 ++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/codestory/http/WebServer.java b/src/main/java/net/codestory/http/WebServer.java index 3f34979..540c924 100644 --- a/src/main/java/net/codestory/http/WebServer.java +++ b/src/main/java/net/codestory/http/WebServer.java @@ -29,12 +29,17 @@ import net.codestory.http.routes.*; import net.codestory.http.ssl.*; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.*; -import javax.net.ssl.*; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -42,7 +47,6 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -98,7 +102,7 @@ public WebServer start(int port) { } public WebServer startSSL(int port, Path pathCertificate, Path pathPrivateKey) { - SSLContext context; + SslContextFactory context; try { context = new SSLContextFactory().create(pathCertificate, pathPrivateKey); } catch (Exception e) { @@ -107,11 +111,25 @@ public WebServer startSSL(int port, Path pathCertificate, Path pathPrivateKey) { return startWithContext(port, context); } - private WebServer startWithContext(int port, SSLContext context) { + private WebServer startWithContext(int port, SslContextFactory context) { try { this.port = Env.INSTANCE.overriddenPort(port); - server = new Server(this.port); + if (context == null) { + server = new Server(this.port); + } else { + server = new Server(); + + HttpConfiguration https = new HttpConfiguration(); + https.addCustomizer(new SecureRequestCustomizer()); + + ServerConnector sslConnector = new ServerConnector(server, + new SslConnectionFactory(context, "http/1.1"), + new HttpConnectionFactory(https)); + sslConnector.setPort(this.port); + + server.addConnector(sslConnector); + } ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); servletHandler.addFilter(new FilterHolder(this), "/*", EnumSet.of(DispatcherType.REQUEST)); diff --git a/src/main/java/net/codestory/http/ssl/SSLContextFactory.java b/src/main/java/net/codestory/http/ssl/SSLContextFactory.java index cb02655..6762b29 100644 --- a/src/main/java/net/codestory/http/ssl/SSLContextFactory.java +++ b/src/main/java/net/codestory/http/ssl/SSLContextFactory.java @@ -15,6 +15,8 @@ */ package net.codestory.http.ssl; +import org.eclipse.jetty.util.ssl.SslContextFactory; + import java.io.*; import java.nio.file.*; import java.security.*; @@ -25,7 +27,8 @@ import javax.net.ssl.*; public class SSLContextFactory { - public SSLContext create(Path pathCertificate, Path pathPrivateKey) throws Exception { + + public SslContextFactory create(Path pathCertificate, Path pathPrivateKey) throws Exception { X509Certificate cert = generateCertificateFromDER(Files.readAllBytes(pathCertificate)); RSAPrivateKey key = generatePrivateKeyFromDER(Files.readAllBytes(pathPrivateKey)); @@ -37,7 +40,9 @@ public SSLContext create(Path pathCertificate, Path pathPrivateKey) throws Excep SSLContext context = SSLContext.getInstance("TLS"); context.init(getKeyManagers(keystore), null, null); - return context; + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setSslContext(context); + return sslContextFactory; } private static KeyManager[] getKeyManagers(KeyStore keyStore) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { diff --git a/src/test/java/net/codestory/http/ssl/SSLTest.java b/src/test/java/net/codestory/http/ssl/SSLTest.java index 062cccb..41a2988 100644 --- a/src/test/java/net/codestory/http/ssl/SSLTest.java +++ b/src/test/java/net/codestory/http/ssl/SSLTest.java @@ -19,18 +19,23 @@ import java.nio.file.*; import java.util.*; +import com.jayway.restassured.RestAssured; import net.codestory.http.*; import org.junit.*; public class SSLTest { @Test - public void start_server() throws URISyntaxException { + public void start_server() throws URISyntaxException, InterruptedException { Path pathCertificate = resource("certificates/server.crt"); Path pathPrivateKey = resource("certificates/server.der"); WebServer webServer = new WebServer(); webServer.startSSL(8183 + new Random().nextInt(1000), pathCertificate, pathPrivateKey); + + RestAssured.useRelaxedHTTPSValidation(); + RestAssured.get("https://localhost:" + webServer.port() + "/").then().statusCode(200); + } private static Path resource(String name) throws URISyntaxException { From d4715ca2e4f1d9459df297e946cbfea5cc7b6604 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 11:14:01 +0100 Subject: [PATCH 06/10] Add init method do call a specific configure method --- .../java/net/codestory/http/WebServer.java | 23 ++++++++++++++++- .../http/servlet/WebServerConfig.java | 25 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/codestory/http/servlet/WebServerConfig.java diff --git a/src/main/java/net/codestory/http/WebServer.java b/src/main/java/net/codestory/http/WebServer.java index 540c924..fb3306c 100644 --- a/src/main/java/net/codestory/http/WebServer.java +++ b/src/main/java/net/codestory/http/WebServer.java @@ -16,6 +16,8 @@ package net.codestory.http; import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.*; import java.nio.file.Path; import java.util.*; @@ -27,6 +29,7 @@ import net.codestory.http.payload.*; import net.codestory.http.reload.*; import net.codestory.http.routes.*; +import net.codestory.http.servlet.WebServerConfig; import net.codestory.http.ssl.*; import org.eclipse.jetty.server.HttpConfiguration; @@ -203,7 +206,25 @@ protected Payload errorPage(Payload payload, Exception e) { @Override public void init(FilterConfig filterConfig) throws ServletException { - // TODO, call spec class no init routes of user. + String configClassName = filterConfig.getInitParameter("configClass"); + if (configClassName == null) { + throw new IllegalArgumentException("Parameter configClass must be specified for the filter."); + } + + try { + Class configClass = Class.forName(configClassName); + Constructor constructor = configClass.getConstructor(); + Object configObject = constructor.newInstance(); + if (!(configObject instanceof WebServerConfig)) { + throw new IllegalArgumentException(configClassName + " must implement WebServerConfig"); + } + WebServerConfig webServerConfig = (WebServerConfig) configObject; + webServerConfig.configure(this); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Parameter configClass must be set with a class", e); + } catch (NoSuchMethodException|InvocationTargetException|InstantiationException|IllegalAccessException e) { + throw new IllegalArgumentException(configClassName + " must have a public constructor with no args", e); + } } @Override diff --git a/src/main/java/net/codestory/http/servlet/WebServerConfig.java b/src/main/java/net/codestory/http/servlet/WebServerConfig.java new file mode 100644 index 0000000..a08184f --- /dev/null +++ b/src/main/java/net/codestory/http/servlet/WebServerConfig.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013- Yan Bonnel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.codestory.http.servlet; + +import net.codestory.http.WebServer; + +public interface WebServerConfig { + + public void configure(WebServer webServer); + +} From 38b278c05c4534c2c347cfe4e253a0adbedc55db Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 12:11:52 +0100 Subject: [PATCH 07/10] Add a test for web.xml --- pom.xml | 6 ++ .../codestory/http/servlet/WebXmlConfig.java | 27 ++++++++ .../codestory/http/servlet/WebXmlTest.java | 67 +++++++++++++++++++ .../codestory/http/servlet/WEB-INF/web.xml | 15 +++++ 4 files changed, 115 insertions(+) create mode 100644 src/test/java/net/codestory/http/servlet/WebXmlConfig.java create mode 100644 src/test/java/net/codestory/http/servlet/WebXmlTest.java create mode 100644 src/test/resources/net/codestory/http/servlet/WEB-INF/web.xml diff --git a/pom.xml b/pom.xml index 4f69dd1..57cafaf 100644 --- a/pom.xml +++ b/pom.xml @@ -310,5 +310,11 @@ 3.1.1 test + + org.eclipse.jetty + jetty-webapp + 9.1.3.v20140225 + test + diff --git a/src/test/java/net/codestory/http/servlet/WebXmlConfig.java b/src/test/java/net/codestory/http/servlet/WebXmlConfig.java new file mode 100644 index 0000000..6ac8ff7 --- /dev/null +++ b/src/test/java/net/codestory/http/servlet/WebXmlConfig.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013- Yan Bonnel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.codestory.http.servlet; + +import net.codestory.http.WebServer; + +public class WebXmlConfig implements WebServerConfig { + @Override + public void configure(WebServer webServer) { + webServer.configure(routes -> + routes.get("/hellowebxml", "Hello World")); + } +} diff --git a/src/test/java/net/codestory/http/servlet/WebXmlTest.java b/src/test/java/net/codestory/http/servlet/WebXmlTest.java new file mode 100644 index 0000000..30630aa --- /dev/null +++ b/src/test/java/net/codestory/http/servlet/WebXmlTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013- Yan Bonnel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.codestory.http.servlet; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.assertion.BodyMatcher; +import com.jayway.restassured.matcher.RestAssuredMatchers; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; + +public class WebXmlTest { + + private Server server; + + private int port; + + @Before + public void startServer() throws Exception { + port = 8080 + new Random().nextInt(1000); + server = new Server(port); + + WebAppContext context = new WebAppContext(WebXmlTest.class.getResource("/net/codestory/http/servlet").toExternalForm(), "/"); + context.setParentLoaderPriority(true); + + server.setHandler(context); + + server.start(); + } + + @Test + public void should_answer_with_webxml() throws Exception { + + RestAssured.port = this.port; + assertThat(RestAssured.get("/hellowebxml").then() + .statusCode(200) + .body(equalTo("Hello World"))); + + } + + @After + public void stopServer() throws Exception { + server.stop(); + } + +} diff --git a/src/test/resources/net/codestory/http/servlet/WEB-INF/web.xml b/src/test/resources/net/codestory/http/servlet/WEB-INF/web.xml new file mode 100644 index 0000000..a067f1c --- /dev/null +++ b/src/test/resources/net/codestory/http/servlet/WEB-INF/web.xml @@ -0,0 +1,15 @@ + + + codestoryfilter + net.codestory.http.WebServer + + configClass + net.codestory.http.servlet.WebXmlConfig + + + + + codestoryfilter + * + + \ No newline at end of file From cc7c4dc01d3cc30c7730a44a850b51c2de832175 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 12:16:33 +0100 Subject: [PATCH 08/10] Add licenses --- .../codestory/http/servlet/WebServerConfig.java | 15 +++++++-------- .../net/codestory/http/servlet/WebXmlConfig.java | 15 +++++++-------- .../net/codestory/http/servlet/WebXmlTest.java | 15 +++++++-------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/codestory/http/servlet/WebServerConfig.java b/src/main/java/net/codestory/http/servlet/WebServerConfig.java index a08184f..3d32016 100644 --- a/src/main/java/net/codestory/http/servlet/WebServerConfig.java +++ b/src/main/java/net/codestory/http/servlet/WebServerConfig.java @@ -1,18 +1,17 @@ -/* - * Copyright 2013- Yan Bonnel +/** + * Copyright (C) 2013 all@code-story.net * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ package net.codestory.http.servlet; diff --git a/src/test/java/net/codestory/http/servlet/WebXmlConfig.java b/src/test/java/net/codestory/http/servlet/WebXmlConfig.java index 6ac8ff7..11e87b0 100644 --- a/src/test/java/net/codestory/http/servlet/WebXmlConfig.java +++ b/src/test/java/net/codestory/http/servlet/WebXmlConfig.java @@ -1,18 +1,17 @@ -/* - * Copyright 2013- Yan Bonnel +/** + * Copyright (C) 2013 all@code-story.net * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ package net.codestory.http.servlet; diff --git a/src/test/java/net/codestory/http/servlet/WebXmlTest.java b/src/test/java/net/codestory/http/servlet/WebXmlTest.java index 30630aa..47fd480 100644 --- a/src/test/java/net/codestory/http/servlet/WebXmlTest.java +++ b/src/test/java/net/codestory/http/servlet/WebXmlTest.java @@ -1,18 +1,17 @@ -/* - * Copyright 2013- Yan Bonnel +/** + * Copyright (C) 2013 all@code-story.net * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ package net.codestory.http.servlet; From dd3caa8652a12a26d99c6620ecf743eb602aa89a Mon Sep 17 00:00:00 2001 From: ybonnel Date: Mon, 10 Mar 2014 12:21:24 +0100 Subject: [PATCH 09/10] Add detection of embedded mode (no need to configClass) --- src/main/java/net/codestory/http/WebServer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/net/codestory/http/WebServer.java b/src/main/java/net/codestory/http/WebServer.java index fb3306c..6dad983 100644 --- a/src/main/java/net/codestory/http/WebServer.java +++ b/src/main/java/net/codestory/http/WebServer.java @@ -117,6 +117,7 @@ public WebServer startSSL(int port, Path pathCertificate, Path pathPrivateKey) { private WebServer startWithContext(int port, SslContextFactory context) { try { this.port = Env.INSTANCE.overriddenPort(port); + embedded = true; if (context == null) { server = new Server(this.port); @@ -204,8 +205,13 @@ protected Payload errorPage(Payload payload, Exception e) { return new ErrorPage(payload, shownError).payload(); } + private boolean embedded = false; + @Override public void init(FilterConfig filterConfig) throws ServletException { + if (embedded) { + return; + } String configClassName = filterConfig.getInitParameter("configClass"); if (configClassName == null) { throw new IllegalArgumentException("Parameter configClass must be specified for the filter."); From 90941ecf6db9276549a2f8af719d8a368f5300f1 Mon Sep 17 00:00:00 2001 From: ybonnel Date: Tue, 11 Mar 2014 21:25:09 +0100 Subject: [PATCH 10/10] Add documentation for servletFilter --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index babff70..c156138 100644 --- a/README.md +++ b/README.md @@ -564,6 +564,58 @@ TODO TODO +## Use in your application server + +If you don't like the embedded server, you can use any application server which support servlet filter. + +First exclude jetty in your pom.xml + +```xml + + net.code-story + http + 1.31 + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-servlet + + + +``` + +Then declare the filter in your web.xml with your class for configuration : +```xml + + codestoryfilter + net.codestory.http.WebServer + + configClass + com.mycompany.ConfigClass + + + + + codestoryfilter + * + +``` + +The class pass throw the parameter configClass must implement WebServerConfig, here is an exemple : +```java +public class ConfigClass implements WebServerConfig { + @Override + public void configure(WebServer webServer) { + webServer.configure(routes -> + routes.get("/hello", "Hello World")); + } +} +``` + # Deploy on Maven Central Build the release: @@ -596,7 +648,6 @@ Synchro to Maven Central is done hourly. + Cleanup Payload class. Make Payload immutable? + Auto reload meme sans les lambda avec capture de variables locales + Singletons qui utilise les annotations standards - + Remplacer Simple par un Servlet Filter qui fonctionne par defaut sur un Jetty Http + Help use local storage + Add your own/reuse Servlet filters + Supporter les coffee et less pré-générés