diff options
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java | 38 | ||||
-rw-r--r-- | webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java | 121 |
3 files changed, 150 insertions, 20 deletions
@@ -112,10 +112,19 @@ NanoHTTPD project currently consist of four parts: * File server serves also very long files without memory overhead. * Contains a built-in list of most common MIME types. * Runtime extension support (extensions that serve particular MIME types) - example extension that serves Markdown formatted files. Simply including an extension JAR in the webserver classpath is enough for the extension to be loaded. -* Simple [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) support via `--cors` +* Simple [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) support via `--cors` paramater * by default serves `Access-Control-Allow-Headers: origin,accept,content-type` * possibility to set `Access-Control-Allow-Headers` by setting System property: `AccessControlAllowHeader` * _example: _ `-DAccessControlAllowHeader=origin,accept,content-type,Authorization` + * possible values: + * `--cors`: activates CORS support, `Access-Control-Allow-Origin` will be set to `*` + * `--cors=some_value`: `Access-Control-Allow-Origin` will be set to `some_value`. + +**_CORS argument examples_** + + +* `--cors=http://appOne.company.com` +* `--cors="http://appOne.company.com, http://appTwo.company.com"`: note the double quotes so that the 2 URLs are considered part of a single argument. ## Maven dependencies diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java index 2da13a1..2855b7b 100644 --- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java +++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java @@ -32,7 +32,6 @@ package fi.iki.elonen; * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -137,7 +136,7 @@ public class SimpleWebServer extends NanoHTTPD { String host = null; // bind to all interfaces by default List<File> rootDirs = new ArrayList<File>(); boolean quiet = false; - boolean cors = false; + String cors = null; Map<String, String> options = new HashMap<String, String>(); // Parse command-line, with short and long versions of the options. @@ -150,8 +149,12 @@ public class SimpleWebServer extends NanoHTTPD { quiet = true; } else if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dir")) { rootDirs.add(new File(args[i + 1]).getAbsoluteFile()); - } else if (args[i].equalsIgnoreCase("--cors")) { - cors = true; + } else if (args[i].startsWith("--cors")) { + cors = "*"; + int equalIdx = args[i].indexOf('='); + if (equalIdx > 0) { + cors = args[i].substring(equalIdx + 1); + } } else if (args[i].equalsIgnoreCase("--licence")) { System.out.println(SimpleWebServer.LICENCE + "\n"); } else if (args[i].startsWith("-X:")) { @@ -167,7 +170,6 @@ public class SimpleWebServer extends NanoHTTPD { if (rootDirs.isEmpty()) { rootDirs.add(new File(".").getAbsoluteFile()); } - options.put("host", host); options.put("port", "" + port); options.put("quiet", String.valueOf(quiet)); @@ -182,7 +184,6 @@ public class SimpleWebServer extends NanoHTTPD { } } options.put("home", sb.toString()); - ServiceLoader<WebServerPluginInfo> serviceLoader = ServiceLoader.load(WebServerPluginInfo.class); for (WebServerPluginInfo info : serviceLoader) { String[] mimeTypes = info.getMimeTypes(); @@ -201,7 +202,6 @@ public class SimpleWebServer extends NanoHTTPD { registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime), options); } } - ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet, cors)); } @@ -226,26 +226,26 @@ public class SimpleWebServer extends NanoHTTPD { private final boolean quiet; - private final boolean cors; + private final String cors; protected List<File> rootDirs; - public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, boolean useCORS) { - this(host, port, Collections.singletonList(wwwroot), quiet, useCORS); + public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, String cors) { + this(host, port, Collections.singletonList(wwwroot), quiet, cors); } public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) { - this(host, port, Collections.singletonList(wwwroot), quiet, false); + this(host, port, Collections.singletonList(wwwroot), quiet, null); } public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) { - this(host, port, wwwroots, quiet, false); + this(host, port, wwwroots, quiet, null); } - public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet, boolean useCORS) { + public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet, String cors) { super(host, port); this.quiet = quiet; - this.cors = useCORS; + this.cors = cors; this.rootDirs = new ArrayList<File>(wwwroots); init(); @@ -405,14 +405,14 @@ public class SimpleWebServer extends NanoHTTPD { private Response respond(Map<String, String> headers, IHTTPSession session, String uri) { // First let's handle CORS OPTION query Response r; - if (cors && Method.OPTIONS.equals(session.getMethod())) { + if (cors != null && Method.OPTIONS.equals(session.getMethod())) { r = new NanoHTTPD.Response(Response.Status.OK, MIME_PLAINTEXT, null, 0); } else { r = defaultRespond(headers, session, uri); } - if (cors) { - r = addCORSHeaders(headers, r); + if (cors != null) { + r = addCORSHeaders(headers, r, cors); } return r; } @@ -621,8 +621,8 @@ public class SimpleWebServer extends NanoHTTPD { return res; } - protected Response addCORSHeaders(Map<String, String> queryHeaders, Response resp) { - resp.addHeader("Access-Control-Allow-Origin", "*"); + protected Response addCORSHeaders(Map<String, String> queryHeaders, Response resp, String cors) { + resp.addHeader("Access-Control-Allow-Origin", cors); resp.addHeader("Access-Control-Allow-Headers", calculateAllowHeaders(queryHeaders)); resp.addHeader("Access-Control-Allow-Credentials", "true"); resp.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); diff --git a/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java new file mode 100644 index 0000000..dbd2c4e --- /dev/null +++ b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java @@ -0,0 +1,121 @@ +package fi.iki.elonen; + +/* + * #%L + * NanoHttpd-Webserver + * %% + * Copyright (C) 2012 - 2015 nanohttpd + * %% + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the nanohttpd nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @author Matthieu Brouillard [matthieu@brouillard.fr] + */ +public class TestCorsHttpServerWithSingleOrigin extends AbstractTestHttpServer { + + private static PipedOutputStream stdIn; + + private static Thread serverStartThread; + + @BeforeClass + public static void setUp() throws Exception { + stdIn = new PipedOutputStream(); + System.setIn(new PipedInputStream(stdIn)); + serverStartThread = new Thread(new Runnable() { + + @Override + public void run() { + String[] args = { + "--host", + "localhost", + "--port", + "9090", + "--dir", + "src/test/resources", + "--cors=http://localhost:9090" + }; + SimpleWebServer.main(args); + } + }); + serverStartThread.start(); + // give the server some tine to start. + Thread.sleep(100); + } + + @AfterClass + public static void tearDown() throws Exception { + stdIn.write("\n\n".getBytes()); + serverStartThread.join(2000); + Assert.assertFalse(serverStartThread.isAlive()); + } + + @Test + public void doTestOption() throws Exception { + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html"); + CloseableHttpResponse response = httpclient.execute(httpOption); + Assert.assertEquals(200, response.getStatusLine().getStatusCode()); + Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); + Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090", + response.getLastHeader("Access-Control-Allow-Origin").getValue()); + response.close(); + } + + @Test + public void doSomeBasicTest() throws Exception { + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html"); + CloseableHttpResponse response = httpclient.execute(httpget); + HttpEntity entity = response.getEntity(); + String string = new String(readContents(entity), "UTF-8"); + + Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); + Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090", + response.getLastHeader("Access-Control-Allow-Origin").getValue()); + Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string); + response.close(); + } +} |