Skip to content

Commit

Permalink
Cors MP example (#1747)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjquinno authored May 7, 2020
1 parent 4df31fd commit 3f20d05
Show file tree
Hide file tree
Showing 10 changed files with 826 additions and 0 deletions.
126 changes: 126 additions & 0 deletions examples/microprofile/cors/README.md
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.


97 changes: 97 additions & 0 deletions examples/microprofile/cors/pom.xml
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>
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();
}
}
Loading

0 comments on commit 3f20d05

Please sign in to comment.