简体   繁体   中英

Correctly configure vert.x server to allow Cross-origin resource sharing (CORS)

I am trying to understand CORS configuration with vert.x. I found an example at this github repository under the CORS section. When i tried it only POST example seemed to work (preflight.html). Since I also need to use GET (nopreflight.html) in one of my projects, I have tried to modify the example obtaining poor results. This is what i have right now:

Server.java

package io.vertx.example.web.cors;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.example.util.Runner;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.StaticHandler;

import java.util.Map;
/*
 * @author <a href="mailto:pmlopes@gmail.com">Paulo Lopes</a>
 */
public class Server extends AbstractVerticle {

    // Convenience method so you can run it in your IDE
    public static void main(String[] args) {
        Runner.runExample(Server.class);
    }

    @Override
    public void start() throws Exception {

        Router router = Router.router(vertx);

        router.route().handler(CorsHandler.create("*")
                           .allowedMethod(HttpMethod.GET)
                           .allowedMethod(HttpMethod.POST)
                           .allowedMethod(HttpMethod.OPTIONS)
                           .allowedHeader("Access-Control-Request-Method")
                           .allowedHeader("Access-Control-Allow-Credentials")
                           .allowedHeader("Access-Control-Allow-Origin")
                           .allowedHeader("Access-Control-Allow-Headers")
                           .allowedHeader("X-PINGARUNER")
                           .allowedHeader("Content-Type"));

        router.get("/access-control-with-get").handler(ctx -> {
            ctx.response().setChunked(true);

            MultiMap headers = ctx.request().headers();
            /*for (String key : headers.names()) {
              ctx.response().write(key);
              ctx.response().write(headers.get(key));
              ctx.response().write("\n");
            }*/
            ctx.response().write("response ");
            ctx.response().end();
        });

        router.post("/access-control-with-post-preflight").handler(ctx -> { ctx.response().setChunked(true);

            MultiMap headers = ctx.request().headers();
            /*for (String key : headers.names()) {
              ctx.response().write(key);
              ctx.response().write(headers.get(key));
              ctx.response().write("\n");
            }*/
            ctx.response().write("response ");
            ctx.response().end();
        });

        // Serve the static resources
        router.route().handler(StaticHandler.create());

        vertx.createHttpServer()
        .requestHandler(router::accept)
        .listen(8080);
    }
}

nopreflight.html

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Simple use of Cross-Site XMLHttpRequest (Using Access Control)</title>
<script type="text/javascript">
        //<![CDATA[

        var invocation = new XMLHttpRequest();
        var url = 'http://localhost:8080/access-control-with-get/';
        var invocationHistoryText;
        var body = '<?xml version="1.0"?><person><name>Arun</name></person>';

        function callOtherDomain(){
            if(invocation)
            {
                invocation.open('GET', url, true);
                //invocation.setRequestHeader('X-PINGARUNER', 'pingpong');
                invocation.setRequestHeader('Content-Type', 'application/xml');
                invocation.onreadystatechange = handler;
                invocation.send();
            }
            else
            {
                invocationHistoryText = "No Invocation TookPlace At All";
                var textNode = document.createTextNode(invocationHistoryText);
                var textDiv = document.getElementById("textDiv");
                textDiv.appendChild(textNode);
            }

        }
        function handler()
        {
            if (invocation.readyState == 4)
            {
                if (invocation.status == 200)
                {
                    var response = invocation.responseXML;
                    //var invocationHistory = response.getElementsByTagName('invocationHistory').item(0).firstChild.data;
                    invocationHistoryText = document.createTextNode(response);
                    var textDiv = document.getElementById("textDiv");
                    textDiv.appendChild(invocationHistoryText);

                }
                else
                    alert("Invocation Errors Occured " + invocation.readyState + " and the status is " + invocation.status);
            }
            else
                console.log("currently the application is at " + invocation.readyState);
        }
        //]]>


    </script>
</head>
<body>
<form id="controlsToInvoke" action="">
    <p>
        <input type="button" value="Click to Invoke Another Site" onclick="callOtherDomain()" />
    </p>
</form>
<p id="intro">
    This page basically makes invocations to another domain using cross-site XMLHttpRequest mitigated by Access Control.  This is the simple scenario that is <em>NOT</em> preflighted, and the invocation to a resource on another domain takes place using a simple HTTP GET.
</p>
<div id="textDiv">
    This XHTML document invokes another resource using cross-site XHR.
</div>
</body>
</html>

preflight.html

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Simple use of Cross-Site XMLHttpRequest (Using Access Control)</title>
<script type="text/javascript">
        //<![CDATA[

        var invocation = new XMLHttpRequest();
        var url = 'http://localhost:8080/access-control-with-post-preflight/';
        var invocationHistoryText;
        var body = '<?xml version="1.0"?><person><name>Arun</name></person>';

        function callOtherDomain(){
            if(invocation)
            {
                invocation.open('POST', url, true);
                invocation.setRequestHeader('X-PINGARUNER', 'pingpong');
                invocation.setRequestHeader('Content-Type', 'application/xml');
                invocation.onreadystatechange = handler;
                invocation.send();
            }
            else
            {
                invocationHistoryText = "No Invocation TookPlace At All";
                var textNode = document.createTextNode(invocationHistoryText);
                var textDiv = document.getElementById("textDiv");
                textDiv.appendChild(textNode);
            }

        }
        function handler()
        {
            if (invocation.readyState == 4)
            {
                if (invocation.status == 200)
                {
                    var response = invocation.responseText;
                    //var invocationHistory = response.getElementsByTagName('invocationHistory').item(0).firstChild.data;
                    invocationHistoryText = document.createTextNode(response);
                    var textDiv = document.getElementById("textDiv");
                    textDiv.appendChild(invocationHistoryText);

                }
                else
                {
                    alert("Invocation Errors Occured " + invocation.readyState + " and the status is " + invocation.status);
                }
            }
            else
            {
                console.log("currently the application is at " + invocation.readyState);
            }
        }
        //]]>


    </script>
</head>
<body>
<form id="controlsToInvoke" action="">
    <p>
        <input type="button" value="Click to Invoke Another Site" onclick="callOtherDomain()" />
    </p>
</form>
<p id="intro">
    This page POSTs XML data to another domain using cross-site XMLHttpRequest mitigated by Access Control.  This is the preflight scenario and the invocation to a resource on another domain takes place using first an OPTIONS request, then an actual POST request.
</p>
<div id="textDiv">
    This XHTML document POSTs to another resource using cross-site XHR.  If you get a response back, the content of that response should reflect what you POSTed.
</div>
</body>
</html>

EDIT: Thanks to a suggestion I modified Server.java code to make it clearer and I understood that the problem was the hanlder function in nopreflight.html file. I solved the problem as follows:

EDITED Server.java

public class Server extends AbstractVerticle {

// Convenience method so you can run it in your IDE
public static void main(String[] args) {
    Runner.runExample(Server.class);
}

@Override
public void start() throws Exception {

    Router router = Router.router(vertx);

    Set<String> allowedHeaders = new HashSet<>();
    allowedHeaders.add("x-requested-with");
    allowedHeaders.add("Access-Control-Allow-Origin");
    allowedHeaders.add("origin");
    allowedHeaders.add("Content-Type");
    allowedHeaders.add("accept");
    allowedHeaders.add("X-PINGARUNER");

    Set<HttpMethod> allowedMethods = new HashSet<>();
    allowedMethods.add(HttpMethod.GET);
    allowedMethods.add(HttpMethod.POST);
    allowedMethods.add(HttpMethod.DELETE);
    allowedMethods.add(HttpMethod.PATCH);
    allowedMethods.add(HttpMethod.OPTIONS);
    allowedMethods.add(HttpMethod.PUT);


    router.route().handler(CorsHandler.create("*")
        .allowedHeaders(allowedHeaders)
        .allowedMethods(allowedMethods));

    router.get("/access-control-with-get").handler(ctx -> {
        HttpServerResponse httpServerResponse = ctx.response();
        httpServerResponse.putHeader("content-type", "text/html").end("<h1>Success</h1>");
    });

EDITED nopreflight.html

<script type="text/javascript">

    var xhttp = new XMLHttpRequest();
    var url = 'http://localhost:8080/access-control-with-get/';
    var invocationHistoryText;

    function callOtherDomain() {
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
                document.getElementById("textDiv").appendChild(document.createTextNode(xhttp.responseText));
            }
        };
        xhttp.open("GET", url, true);
        xhttp.send();
    }

</script>

CorsHandler.create("*") will not work.
You will need to provide a regex String on CorsHandler.create("Regex String Here") . That Regex String will need to be a valid Java regex that will work on Pattern.compile("Regex String Here") . So if you want to allow any protocol:host:port, aka "*", through CORS handling you can use.

router.route().handler(CorsHandler.create(".*.");  //note the "." surrounding "*"

If you want a fine-grained control of allowed protocol:host:port, you can be creative with the Regex String. For example, to set CORS handling for either http:// or https:// from localhost and any port:

router.route().handler(CorsHandler.create("((http:\\/\\/)|(https:\\/\\/))localhost:\\d+");  

You can also use one handler to allow a whitelist of origins, example:

router.route().handler(CorsHandler.create("http:\\/\\/localhost:8080|https:\\/\\/128.32.24.45:\\d+|http:\\/\\/121.0.4.3:8080"));

This seems to be working fine for us

    Router router = Router.router(vertx);

    Set<String> allowedHeaders = new HashSet<>();
    allowedHeaders.add("x-requested-with");
    allowedHeaders.add("Access-Control-Allow-Origin");
    allowedHeaders.add("origin");
    allowedHeaders.add("Content-Type");
    allowedHeaders.add("accept");

    Set<HttpMethod> allowedMethods = new HashSet<>();
    allowedMethods.add(HttpMethod.GET);
    allowedMethods.add(HttpMethod.POST);
    allowedMethods.add(HttpMethod.DELETE);
    allowedMethods.add(HttpMethod.PATCH);
    allowedMethods.add(HttpMethod.OPTIONS);
    allowedMethods.add(HttpMethod.PUT);

    router.route().handler(CorsHandler.create("*")
            .allowedHeaders(allowedHeaders)
            .allowedMethods(allowedMethods));

    router.get("/").handler(context1 -> {
        HttpServerResponse httpServerResponse = context1.response();
        httpServerResponse.putHeader("content-type", "text/html").end("<h1>Success</h1>");
    });

after this if you create httpserver, it should work fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM