Skip to content

Instantly share code, notes, and snippets.

@mhagnumdw
Created September 27, 2018 15:49
Show Gist options
  • Save mhagnumdw/741f71d422e59251ddcde6f404603f42 to your computer and use it in GitHub Desktop.
Save mhagnumdw/741f71d422e59251ddcde6f404603f42 to your computer and use it in GitHub Desktop.
CorsHandler for Pippo
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.route.RouteContext;
import ro.pippo.core.route.RouteHandler;
/**
* Define how CORS requests are handled.
*
* <p>The Cross-Origin Resource Sharing standard works by adding new HTTP headers
* that allow servers to describe the set of origins that are permitted to read
* that information using a web browser.</p>
*
* <p>For more details see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS</p>
*
* <p>Based on: https://github.com/pac4j/pac4j/blob/3806174df54b939ed2785ee493f63b9851fcd03e/pac4j-core/src/main/java/org/pac4j/core/authorization/authorizer/CorsAuthorizer.java</p>
*/
public class CorsHandler implements RouteHandler<RouteContext> {
private static final Logger log = LoggerFactory.getLogger(CorsHandler.class);
private String allowOrigin;
private String exposeHeaders;
private int maxAge = -1;
private Boolean allowCredentials;
private String allowMethods;
private String allowHeaders;
public static Builder builder() {
return new Builder();
}
private CorsHandler(Builder builder) {
this.allowOrigin = builder.allowOrigins.stream().collect(Collectors.joining(", "));
if (StringUtils.isBlank(this.allowOrigin)) {
throw new RuntimeException("allowOrigin cannot be blank");
}
this.exposeHeaders = builder.exposeHeaders.stream().collect(Collectors.joining(", "));
this.maxAge = builder.maxAge;
this.allowCredentials = builder.allowCredentials;
this.allowMethods = builder.allowMethods.stream().collect(Collectors.joining(", "));
this.allowHeaders = builder.allowHeaders.stream().collect(Collectors.joining(", "));
log.info(String.format("CorsHandler [allowOrigin=%s, allowMethods=%s, allowHeaders=%s, exposeHeaders=%s, maxAge=%s, allowCredentials=%s]", allowOrigin, allowMethods, allowHeaders,
exposeHeaders, maxAge, allowCredentials));
}
// TODO: submitted pull request to Pippo to create the constants below.
// Change the Strings of the method below by the constants when possible:
// See: https://github.com/pippo-java/pippo/pull/452
// 1
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_ALLOW_ORIGIN
// value: "Access-Control-Allow-Origin"
// 2
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_EXPOSE_HEADERS
// value: "Access-Control-Expose-Headers"
// 3
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_MAX_AGE
// value: "Access-Control-Max-Age"
// 4
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_ALLOW_CREDENTIALS
// value: "Access-Control-Allow-Credentials"
// 5
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_ALLOW_METHODS
// value: "Access-Control-Allow-Methods"
// 6
// attribute: ro.pippo.core.HttpConstants.Header.ACCESS_CONTROL_ALLOW_HEADERS
// value: "Access-Control-Allow-Headers"
@Override
public void handle(RouteContext context) {
context.getResponse().header("Access-Control-Allow-Origin", allowOrigin);
if (StringUtils.isNotBlank(exposeHeaders)) {
context.getResponse().header("Access-Control-Expose-Headers", exposeHeaders);
}
if (maxAge != -1) {
context.getResponse().header("Access-Control-Max-Age", "" + maxAge);
}
// According to the documentation only if true is what needs to be set
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials#Directives
if (allowCredentials != null && allowCredentials) {
context.getResponse().header("Access-Control-Allow-Credentials", allowCredentials.toString());
}
if (allowMethods != null) {
context.getResponse().header("Access-Control-Allow-Methods", allowMethods);
}
if (allowHeaders != null) {
context.getResponse().header("Access-Control-Allow-Headers", allowHeaders);
}
if (context.getRequestMethod().equals("OPTIONS")) {
context.getResponse().accepted();
return;
}
context.next();
}
public static class Builder {
private Set<String> allowOrigins = new HashSet<>();
private Set<String> exposeHeaders = new HashSet<>();
private int maxAge = -1;
private Boolean allowCredentials;
private Set<String> allowMethods = new HashSet<>();
private Set<String> allowHeaders = new HashSet<>();
private Builder() {
}
/**
* Creates an instance of CorsHandler.
*
* @return instance of CorsHandler
*/
public CorsHandler build() {
return new CorsHandler(this);
}
/**
* <b>Required!</b> The {@code Access-Control-Allow-Origin} response header indicates whether the response can be shared with requesting code from the given origin.
*
* @param origin
* origin, eg: http://pippo.ro
*
* @return this
*/
public Builder addAllowOrigin(String origin) {
this.allowOrigins.add(origin);
return this;
}
/**
* The {@code Access-Control-Expose-Headers} response header indicates which headers can be exposed as part of the response by listing their names.
*
* @param header
* header name
* @return this
*/
public Builder addExposeHeader(String header) {
this.exposeHeaders.add(header);
return this;
}
/**
* The {@code Access-Control-Max-Age} response header indicates how long the results of a preflight request (that is the information contained in the {@code Access-Control-Allow-Methods} and
* {@code Access-Control-Allow-Headers} headers) can be cached.
*
* @param maxAgeInSeconds
* max age in seconds
*
* @return this
*/
public Builder setMaxAge(int maxAgeInSeconds) {
this.maxAge = maxAgeInSeconds;
return this;
}
/**
* The Access-Control-Allow-Credentials response header indicates whether or not the response to the request can be exposed to the page.
*
* @param allowCredentials
* true to expose, false otherwise
*
* @return this
*/
public Builder allowCredentials(boolean allowCredentials) {
this.allowCredentials = allowCredentials;
return this;
}
/**
* The Access-Control-Allow-Methods response header specifies the method or methods allowed when accessing the resource in response to a preflight request.
*
* @param method
* http method
*
* @return this
*/
public Builder addAllowMethod(String method) {
this.allowMethods.add(method);
return this;
}
/**
* The {@code Access-Control-Allow-Headers} response header is used in response to a preflight request which includes the {@code Access-Control-Request-Headers} to indicate which HTTP headers
* can be used during the actual request.
*
* @param header
* http header
*
* @return this
*/
public Builder addAllowHeader(String header) {
this.allowHeaders.add(header);
return this;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment