├── .travis.yml ├── src ├── test │ ├── webapp │ │ ├── jsp │ │ │ └── render.jsp │ │ └── WEB-INF │ │ │ └── web.xml │ └── java │ │ └── com │ │ └── ghosthack │ │ └── turismo │ │ ├── example │ │ ├── AppRoutes.java │ │ ├── WebAppRoutes.java │ │ ├── JettyHelper.java │ │ ├── ExampleAppRoutes.java │ │ ├── ExampleWebAppRoutes.java │ │ └── ExampleAppRoutesList.java │ │ ├── HttpMocks.java │ │ └── routes │ │ └── RoutesMapTest.java └── main │ └── java │ └── com │ └── ghosthack │ └── turismo │ ├── multipart │ ├── package.html │ ├── ParseException.java │ ├── Parametrizable.java │ ├── MultipartRequest.java │ ├── MultipartFilter.java │ └── MultipartParser.java │ ├── Routes.java │ ├── action │ ├── NotFoundAction.java │ ├── behavior │ │ ├── MovedTemporarily.java │ │ ├── MovedPermanently.java │ │ ├── Redirect.java │ │ ├── StringPrinter.java │ │ ├── NotFound.java │ │ └── Alias.java │ ├── ActionException.java │ └── Action.java │ ├── Resolver.java │ ├── routes │ ├── ExtendedRoutesMap.java │ ├── RoutesMap.java │ └── RoutesList.java │ ├── resolver │ ├── MethodPathResolver.java │ ├── MapResolver.java │ └── ListResolver.java │ ├── util │ └── ClassForName.java │ └── servlet │ ├── Env.java │ └── Servlet.java ├── .gitignore ├── LICENSE.txt ├── pom.xml └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /src/test/webapp/jsp/render.jsp: -------------------------------------------------------------------------------- 1 | <%@ page pageEncoding="UTF-8" %> 2 | <%=request.getAttribute("message")%> -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/main/webapp/WEB-INF/classes/ 2 | target/ 3 | .settings/ 4 | .project 5 | *.DS_Store 6 | .classpath 7 | settings.xml 8 | .idea 9 | turismo.iml 10 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | Partial rfc1867 (multipart-form-data) parser. 8 | 9 | 10 | Contains also a and servlet filter implementation to transparently handle uploads. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/ParseException.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | /** 4 | * This exception is used by the multipar parser 5 | * 6 | * @see MultipartParser 7 | */ 8 | public class ParseException extends Exception { 9 | ParseException(String desc) { 10 | super(desc); 11 | } 12 | 13 | ParseException() { 14 | super(); 15 | } 16 | 17 | /** 18 | * Serializable implementation specific. 19 | */ 20 | private static final long serialVersionUID = 1L; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Adrian Fernandez 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/AppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import com.ghosthack.turismo.action.Action; 4 | import com.ghosthack.turismo.routes.RoutesMap; 5 | 6 | public class AppRoutes extends RoutesMap { 7 | 8 | @Override 9 | protected void map() { 10 | get("/", new Action() { 11 | @Override 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | 18 | public static void main(String[] args) throws Exception{ 19 | JettyHelper.server(8080, "/app/*", AppRoutes.class.getName()); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/WebAppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | 4 | import com.ghosthack.turismo.action.Action; 5 | import com.ghosthack.turismo.routes.RoutesMap; 6 | 7 | public class WebAppRoutes extends RoutesMap { 8 | 9 | @Override 10 | protected void map() { 11 | get("/", new Action() { 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | 18 | /** 19 | * Minics an application server environment 20 | */ 21 | public static void main(String[] args) throws Exception{ 22 | JettyHelper.server(8080, JettyHelper.webapp()); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/HttpMocks.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.when; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | public class HttpMocks { 10 | 11 | public static HttpServletRequest getRequestMock(String method, String path) { 12 | HttpServletRequest req = mock(HttpServletRequest.class); 13 | when(req.getMethod()).thenReturn(method); 14 | when(req.getPathInfo()).thenReturn(path); 15 | return req; 16 | } 17 | 18 | public static HttpServletResponse getResponseMock() { 19 | HttpServletResponse res = mock(HttpServletResponse.class); 20 | return res; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 |39 | * Resolves an action based on the request, Each route executes an 40 | * action. On init configures the {@link Routes} {@link Resolver}. 41 | * 42 | *
43 | * <servlet> 44 | * <servlet-name>app-action-servlet</servlet-name> 45 | * <servlet-class>action.Servlet</servlet-class> 46 | * <init-param> 47 | * <param-name>routes</param-name> 48 | * <param-value>example.Routes</param-value> 49 | * </init-param> 50 | * </servlet> 51 | * <servlet-mapping> 52 | * <servlet-name>app-action-servlet</servlet-name> 53 | * <url-pattern>/app/*</url-pattern> 54 | * </servlet-mapping> 55 | *56 | */ 57 | public class Servlet extends HttpServlet { 58 | 59 | private static final String ROUTES = "routes"; 60 | private static final long serialVersionUID = 1L; 61 | 62 | protected transient Routes routes; 63 | protected transient ServletContext context; 64 | 65 | @Override 66 | public void service(HttpServletRequest req, HttpServletResponse res) 67 | throws ServletException, IOException { 68 | Env.create(req, res, context); 69 | try { 70 | final Runnable action = routes.getResolver().resolve(); 71 | action.run(); 72 | } catch (ActionException e) { 73 | throw new ServletException(e); 74 | } finally { 75 | Env.destroy(); 76 | } 77 | } 78 | 79 | @Override 80 | public void init(ServletConfig config) throws ServletException { 81 | super.init(); 82 | context = config.getServletContext(); 83 | final String routesParam = config.getInitParameter(ROUTES); 84 | try { 85 | routes = createInstance(routesParam, Routes.class); 86 | } catch (ClassForNameException e) { 87 | throw new ServletException(e); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/routes/RoutesMapTest.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.routes; 2 | 3 | import static com.ghosthack.turismo.HttpMocks.getRequestMock; 4 | import static com.ghosthack.turismo.HttpMocks.getResponseMock; 5 | import static org.mockito.Mockito.verify; 6 | 7 | import java.io.IOException; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | import com.ghosthack.turismo.servlet.Env; 16 | 17 | public class RoutesMapTest { 18 | 19 | private RoutesMap routes; 20 | 21 | @Before 22 | public void setUp() { 23 | routes = new RoutesMap() { 24 | @Override 25 | protected void map() { 26 | // TEST GET 27 | get("/", new Runnable() { 28 | @Override 29 | public void run(){ 30 | Env.res().setStatus(200); 31 | } 32 | }); 33 | get("/foo", new Runnable() { 34 | @Override 35 | public void run() { 36 | Env.res().setStatus(201); 37 | } 38 | }); 39 | put("/", new Runnable() { 40 | @Override 41 | public void run() { 42 | Env.res().setStatus(202); 43 | } 44 | }); 45 | post("/bar", new Runnable() { 46 | @Override 47 | public void run() { 48 | Env.res().setStatus(203); 49 | } 50 | }); 51 | route(new Runnable() { 52 | @Override 53 | public void run() { 54 | Env.res().setStatus(404); 55 | } 56 | }); 57 | } 58 | }; 59 | } 60 | 61 | @Test 62 | public void testGET1() throws IOException { 63 | 64 | HttpServletRequest req = getRequestMock("GET", "/"); 65 | HttpServletResponse res = getResponseMock(); 66 | Env.create(req, res, null); 67 | routes.getResolver().resolve().run(); 68 | 69 | verify(res).setStatus(200); 70 | } 71 | 72 | @Test 73 | public void testGET2() throws IOException { 74 | 75 | HttpServletRequest req = getRequestMock("GET", "/foo"); 76 | HttpServletResponse res = getResponseMock(); 77 | Env.create(req, res, null); 78 | routes.getResolver().resolve().run(); 79 | 80 | verify(res).setStatus(201); 81 | } 82 | 83 | @Test 84 | public void testPUT() throws IOException { 85 | 86 | HttpServletRequest req = getRequestMock("PUT", "/"); 87 | HttpServletResponse res = getResponseMock(); 88 | Env.create(req, res, null); 89 | routes.getResolver().resolve().run(); 90 | 91 | verify(res).setStatus(202); 92 | } 93 | 94 | @Test 95 | public void testPOST() throws IOException { 96 | 97 | HttpServletRequest req = getRequestMock("POST", "/bar"); 98 | HttpServletResponse res = getResponseMock(); 99 | Env.create(req, res, null); 100 | routes.getResolver().resolve().run(); 101 | 102 | verify(res).setStatus(203); 103 | } 104 | 105 | @Test 106 | public void testNotFound() throws IOException { 107 | 108 | HttpServletRequest req = getRequestMock("POST", "/everyThingElse"); 109 | HttpServletResponse res = getResponseMock(); 110 | Env.create(req, res, null); 111 | routes.getResolver().resolve().run(); 112 | 113 | verify(res).setStatus(404); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/MultipartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletRequestWrapper; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Collections; 9 | import java.util.Enumeration; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Wrapper that stores multipart form parameters. 15 | * 16 | */ 17 | public class MultipartRequest extends HttpServletRequestWrapper implements 18 | Parametrizable { 19 | 20 | /** Multipart form data boundary key */ 21 | public static final String MULTIPART_FORM_DATA_BOUNDARY = "multipart/form-data; boundary="; 22 | 23 | private static final String BOUNDARY_HEAD = "--"; 24 | private static final int MULTIPART_SIZE = MULTIPART_FORM_DATA_BOUNDARY 25 | .length(); 26 | 27 | private final Map
15 | * If you have a custom Request object added to the filter chain, this filter 16 | * should be the first. 17 | *
18 | *19 | * The file parameter data is added as a byte array attribute with the same 20 | * parameter name.
Example: 21 | * 22 | *33 | * 34 | * The file-name and content-type data sent by the browser are stored as a 35 | * String array in the request object.23 | * <input type="file" name="imageFile"/> 24 | *25 | * 26 | * File contents are be obtained via: 27 | * 28 | *29 | *31 | * 32 | * for later manipulation.byte[] imageFileBytes = request.getAttribute("imageFile");30 | *
Example: 36 | * 37 | *49 | * 50 | * 51 | *38 | * <input type="file" name="imageFile"/> 39 | *40 | * 41 | * File name and content type are obtained like this: 42 | * 43 | *44 | *47 | * 48 | *String contentType = request.getParameter("imageFile")[0];45 | *String fileName = request.getParameter("imageFile")[1];46 | *
52 | * Configuration details: 53 | * 54 | *
55 | * <filter> 56 | * <filter-name>multipart-filter</filter-name> 57 | * <filter-class>multipart.Filter</filter-class> 58 | * <init-param> 59 | * <param-name>charset-name</param-name> 60 | * <param-value>ISO-8859-1</param-value> 61 | * </init-param> 62 | * </filter> 63 | * <filter-mapping> 64 | * <filter-name>multipart-filter</filter-name> 65 | * <url-pattern>/eon/*</url-pattern> 66 | * </filter-mapping> 67 | *68 | * 69 | * 70 | * 71 | */ 72 | public class MultipartFilter implements javax.servlet.Filter { 73 | 74 | private static final String CHARSET_NAME_PARAMETER = "charset-name"; 75 | public static String CHARSET_NAME = "ISO-8859-1"; 76 | 77 | // private static final java.util.logging.Logger LOG = 78 | // java.util.logging.Logger.getLogger(Filter.class.getName()); 79 | 80 | /** 81 | * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 82 | * javax.servlet.ServletResponse, javax.servlet.FilterChain) 83 | */ 84 | public void doFilter(ServletRequest request, ServletResponse response, 85 | FilterChain chain) throws ServletException, IOException { 86 | 87 | final String contentType = request.getContentType(); 88 | if (contentType != null 89 | && contentType.startsWith(MultipartRequest.MULTIPART_FORM_DATA_BOUNDARY)) { 90 | final MultipartRequest multipartRequest; 91 | try { 92 | multipartRequest = MultipartRequest.wrapAndParse((HttpServletRequest) request); 93 | } catch (ParseException e) { 94 | dump(request); 95 | throw new ServletException(e); 96 | } 97 | chain.doFilter(multipartRequest, response); 98 | } else { 99 | chain.doFilter(request, response); 100 | } 101 | 102 | } 103 | 104 | private void dump(ServletRequest request) throws IOException { 105 | // final String data = new Dumper().dump(request.getInputStream(), 106 | // Filter.CHARSET_NAME); 107 | // LOG.info(data); 108 | } 109 | 110 | /** 111 | * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 112 | */ 113 | public void init(FilterConfig config) throws ServletException { 114 | final String charsetName = config 115 | .getInitParameter(CHARSET_NAME_PARAMETER); 116 | if (charsetName != null) { 117 | MultipartFilter.CHARSET_NAME = charsetName; 118 | } 119 | } 120 | 121 | /** 122 | * @see javax.servlet.Filter#destroy() 123 | */ 124 | public void destroy() { 125 | // nothing today 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | turismo -- a sinatra-like Java web framework. 2 | ============================================= 3 | 4 | [](https://travis-ci.org/ghosthack/turismo) [](https://javadoc.io/doc/com.ghosthack/turismo) [](https://maven-badges.herokuapp.com/maven-central/com.ghosthack/turismo) 5 | 6 | Quick intro 7 | ----------- 8 | ```java 9 | public class AppRoutes extends RoutesList { 10 | protected void map() { 11 | get("/", new Action() { 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | } 18 | ``` 19 | 20 | Using wildcards and resource identifiers 21 | ---------------------------------------- 22 | 23 | ```java 24 | public class AppRoutes extends RoutesList { 25 | protected void map() { 26 | get("/wildcard/*/:id", new Action() { 27 | public void run() { 28 | String id = params("id"); 29 | print("wildcard id " + id); 30 | } 31 | }); 32 | get("/alias/*/:id", "/wildcard/*/:id"); 33 | } 34 | } 35 | ``` 36 | 37 | Testing with standalone jetty 38 | ----------------------------- 39 | 40 | ```java 41 | package com.ghosthack.turismo.example; 42 | 43 | import com.ghosthack.turismo.action.*; 44 | import com.ghosthack.turismo.routes.*; 45 | 46 | public class AppRoutes extends RoutesList { 47 | 48 | @Override 49 | protected void map() { 50 | get("/", new Action() { 51 | @Override 52 | public void run() { 53 | print("Hello World!"); 54 | } 55 | }); 56 | } 57 | 58 | public static void main(String[] args) throws Exception{ 59 | JettyHelper.server(8080, "/*", AppRoutes.class.getName()); 60 | } 61 | 62 | } 63 | ``` 64 | 65 | Getting started, as webapp 66 | -------------------------- 67 | 68 | Using a webapp descriptor: `web.xml` 69 | 70 | ```xml 71 | 72 |