-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
826 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Helidon MP CORS Example | ||
|
||
This example shows a simple greeting application, similar to the one from the | ||
Helidon MP QuickStart, enhanced with CORS support. | ||
|
||
Near the end of the `resources/logging.properties` file, a commented line would turn on `FINE` | ||
logging that would reveal how the Helidon CORS support makes it decisions. To see that logging, | ||
uncomment that line and then package and run the application. | ||
|
||
## Build and run | ||
|
||
```bash | ||
mvn package | ||
java -jar target/helidon-examples-microprofile-cors.jar | ||
``` | ||
|
||
## Using the app endpoints as with the "classic" greeting app | ||
|
||
These normal greeting app endpoints work just as in the original greeting app: | ||
|
||
```bash | ||
curl -X GET http://localhost:8080/greet | ||
{"message":"Hello World!"} | ||
|
||
curl -X GET http://localhost:8080/greet/Joe | ||
{"message":"Hello Joe!"} | ||
|
||
curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting | ||
|
||
curl -X GET http://localhost:8080/greet/Jose | ||
{"message":"Hola Jose!"} | ||
``` | ||
|
||
## Using CORS | ||
|
||
### Sending "simple" CORS requests | ||
|
||
The following requests illustrate the CORS protocol with the example app. | ||
|
||
By setting `Origin` and `Host` headers that do not indicate the same system we trigger CORS processing in the | ||
server: | ||
|
||
```bash | ||
# Follow the CORS protocol for GET | ||
curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet | ||
|
||
HTTP/1.1 200 OK | ||
Access-Control-Allow-Origin: * | ||
Content-Type: application/json | ||
Date: Thu, 30 Apr 2020 17:25:51 -0500 | ||
Vary: Origin | ||
connection: keep-alive | ||
content-length: 27 | ||
|
||
{"greeting":"Hola World!"} | ||
``` | ||
Note the new headers `Access-Control-Allow-Origin` and `Vary` in the response. | ||
|
||
The same happens for a `GET` requesting a personalized greeting (by passing the name of the | ||
person to be greeted): | ||
```bash | ||
curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet/Joe | ||
{"greeting":"Hola Joe!"} | ||
``` | ||
Take a look at `GreetResource` and in particular the methods named `optionsForXXX` near the end of the class. | ||
There is one for each different subpath that the resource's endpoints handle: no subpath, `/{name}`, and `/greeting`. The | ||
`@CrossOrigin` annotation on each defines the CORS behavior for the corresponding path. | ||
The `optionsForUpdatingGreeting` gives specific origins and the HTTP method (`PUT`) constraints for sharing that | ||
resource. The other two `optionsForRetrievingXXXGreeting` methods use default parameters for the `@CrossOrigin` | ||
annotation: allowing all origins, all methods, etc. | ||
|
||
With this in mind, we can see why the two earlier `GET` `curl` requests work. | ||
|
||
These are what CORS calls "simple" requests; the CORS protocol for these adds headers to the request and response that | ||
would be exchanged between the client and server even without CORS. | ||
|
||
### "Non-simple" CORS requests | ||
|
||
The CORS protocol requires the client to send a _pre-flight_ request before sending a request | ||
that changes state on the server, such as `PUT` or `DELETE` and to check the returned status | ||
and headers to make sure the server is willing to accept the actual request. CORS refers to such `PUT` and `DELETE` | ||
requests as "non-simple" ones. | ||
|
||
This command sends a pre-flight `OPTIONS` request to see if the server will accept a subsequent `PUT` request from the | ||
specified origin to change the greeting: | ||
```bash | ||
curl -i -X OPTIONS \ | ||
-H "Access-Control-Request-Method: PUT" \ | ||
-H "Origin: http://foo.com" \ | ||
-H "Host: here.com" \ | ||
http://localhost:8080/greet/greeting | ||
|
||
HTTP/1.1 200 OK | ||
Access-Control-Allow-Methods: PUT | ||
Access-Control-Allow-Origin: http://foo.com | ||
Date: Thu, 30 Apr 2020 17:30:59 -0500 | ||
transfer-encoding: chunked | ||
connection: keep-alive | ||
``` | ||
The successful status and the returned `Access-Control-Allow-xxx` headers indicate that the | ||
server accepted the pre-flight request. That means it is OK for us to send `PUT` request to perform the actual change | ||
of greeting. (See below for how the server rejects a pre-flight request.) | ||
```bash | ||
curl -i -X PUT \ | ||
-H "Origin: http://foo.com" \ | ||
-H "Host: here.com" \ | ||
-H "Access-Control-Allow-Methods: PUT" \ | ||
-H "Access-Control-Allow-Origin: http://foo.com" \ | ||
-H "Content-Type: application/json" \ | ||
-d "{ \"greeting\" : \"Cheers\" }" \ | ||
http://localhost:8080/greet/greeting | ||
|
||
HTTP/1.1 204 No Content | ||
Access-Control-Allow-Origin: http://foo.com | ||
Date: Thu, 30 Apr 2020 17:32:55 -0500 | ||
Vary: Origin | ||
connection: keep-alive | ||
``` | ||
And we run one more `GET` to observe the change in the greeting: | ||
```bash | ||
curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet/Joe | ||
{"greeting":"Cheers Joe!"} | ||
``` | ||
Note that the tests in the example `TestCORS` class follow these same steps. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
Copyright (c) 2020 Oracle and/or its affiliates. | ||
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. | ||
--> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>io.helidon.applications</groupId> | ||
<artifactId>helidon-mp</artifactId> | ||
<version>2.0.0-SNAPSHOT</version> | ||
<relativePath>../../../applications/mp/pom.xml</relativePath> | ||
</parent> | ||
<groupId>io.helidon.examples.microprofile</groupId> | ||
<artifactId>helidon-examples-microprofile-cors</artifactId> | ||
<name>Helidon Microprofile Example CORS</name> | ||
|
||
<description> | ||
Microprofile example showing CORS support | ||
</description> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.helidon.microprofile.bundles</groupId> | ||
<artifactId>helidon-microprofile</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.helidon.microprofile</groupId> | ||
<artifactId>helidon-microprofile-cors</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.glassfish.jersey.media</groupId> | ||
<artifactId>jersey-media-json-binding</artifactId> | ||
<scope>runtime</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss</groupId> | ||
<artifactId>jandex</artifactId> | ||
<scope>runtime</scope> | ||
<optional>true</optional> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.hamcrest</groupId> | ||
<artifactId>hamcrest-all</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.helidon.webclient</groupId> | ||
<artifactId>helidon-webclient</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-dependency-plugin</artifactId> | ||
<executions> | ||
<execution> | ||
<id>copy-libs</id> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.jboss.jandex</groupId> | ||
<artifactId>jandex-maven-plugin</artifactId> | ||
<executions> | ||
<execution> | ||
<id>make-index</id> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
153 changes: 153 additions & 0 deletions
153
.../microprofile/cors/src/main/java/io/helidon/microprofile/examples/cors/GreetResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* Copyright (c) 2020 Oracle and/or its affiliates. | ||
* | ||
* 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 io.helidon.microprofile.examples.cors; | ||
|
||
import java.util.Collections; | ||
|
||
import javax.enterprise.context.RequestScoped; | ||
import javax.inject.Inject; | ||
import javax.json.Json; | ||
import javax.json.JsonBuilderFactory; | ||
import javax.json.JsonObject; | ||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.HttpMethod; | ||
import javax.ws.rs.OPTIONS; | ||
import javax.ws.rs.PUT; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.PathParam; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
|
||
import io.helidon.microprofile.cors.CrossOrigin; | ||
|
||
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; | ||
import org.eclipse.microprofile.openapi.annotations.media.Content; | ||
import org.eclipse.microprofile.openapi.annotations.media.Schema; | ||
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; | ||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; | ||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; | ||
|
||
/** | ||
* A simple JAX-RS resource to greet you with CORS support. | ||
*/ | ||
@Path("/greet") | ||
@RequestScoped | ||
public class GreetResource { | ||
|
||
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); | ||
|
||
/** | ||
* The greeting message provider. | ||
*/ | ||
private final GreetingProvider greetingProvider; | ||
|
||
/** | ||
* Using constructor injection to get a configuration property. | ||
* By default this gets the value from META-INF/microprofile-config | ||
* | ||
* @param greetingConfig the configured greeting message | ||
*/ | ||
@Inject | ||
public GreetResource(GreetingProvider greetingConfig) { | ||
this.greetingProvider = greetingConfig; | ||
} | ||
|
||
/** | ||
* Return a worldly greeting message. | ||
* | ||
* @return {@link JsonObject} | ||
*/ | ||
@GET | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public JsonObject getDefaultMessage() { | ||
return createResponse("World"); | ||
} | ||
|
||
/** | ||
* Return a greeting message using the name that was provided. | ||
* | ||
* @param name the name to greet | ||
* @return {@link JsonObject} | ||
*/ | ||
@Path("/{name}") | ||
@GET | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public JsonObject getMessage(@PathParam("name") String name) { | ||
return createResponse(name); | ||
} | ||
|
||
/** | ||
* Set the greeting to use in future messages. | ||
* | ||
* @param jsonObject JSON containing the new greeting | ||
* @return {@link Response} | ||
*/ | ||
@Path("/greeting") | ||
@PUT | ||
@Consumes(MediaType.APPLICATION_JSON) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RequestBody(name = "greeting", | ||
required = true, | ||
content = @Content(mediaType = "application/json", | ||
schema = @Schema(type = SchemaType.STRING, example = "{\"greeting\" : \"Hola\"}"))) | ||
@APIResponses({ | ||
@APIResponse(name = "normal", responseCode = "204", description = "Greeting updated"), | ||
@APIResponse(name = "missing 'greeting'", responseCode = "400", | ||
description = "JSON did not contain setting for 'greeting'")}) | ||
public Response updateGreeting(JsonObject jsonObject) { | ||
|
||
if (!jsonObject.containsKey("greeting")) { | ||
JsonObject entity = JSON.createObjectBuilder() | ||
.add("error", "No greeting provided") | ||
.build(); | ||
return Response.status(Response.Status.BAD_REQUEST).entity(entity).build(); | ||
} | ||
|
||
String newGreeting = jsonObject.getString("greeting"); | ||
|
||
greetingProvider.setMessage(newGreeting); | ||
return Response.status(Response.Status.NO_CONTENT).build(); | ||
} | ||
|
||
@OPTIONS | ||
@Path("/greeting") | ||
@CrossOrigin(value = {"http://foo.com", "http://there.com"}, | ||
allowMethods = {HttpMethod.PUT}) | ||
public void optionsForUpdatingGreeting() { | ||
} | ||
|
||
@OPTIONS | ||
@CrossOrigin() | ||
public void optionsForRetrievingUnnamedGreeting() { | ||
} | ||
|
||
@OPTIONS | ||
@CrossOrigin() | ||
@Path("/{name}") | ||
public void optionsForRetrievingNamedGreeting() { | ||
} | ||
|
||
private JsonObject createResponse(String who) { | ||
String msg = String.format("%s %s!", greetingProvider.getMessage(), who); | ||
|
||
return JSON.createObjectBuilder() | ||
.add("message", msg) | ||
.build(); | ||
} | ||
} |
Oops, something went wrong.