mirror of
https://github.com/google/nomulus.git
synced 2025-07-05 10:43:26 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
64
java/google/registry/request/Action.java
Normal file
64
java/google/registry/request/Action.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Annotation for {@link Runnable} actions accepting HTTP requests from {@link RequestHandler}. */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Action {
|
||||
|
||||
/** HTTP methods recognized by the request processor. */
|
||||
public enum Method { GET, HEAD, POST }
|
||||
|
||||
/** HTTP path to serve the action from. The path components must be percent-escaped. */
|
||||
String path();
|
||||
|
||||
/** Indicates all paths starting with this path should be accepted. */
|
||||
boolean isPrefix() default false;
|
||||
|
||||
/** HTTP methods that request processor should allow. */
|
||||
Method[] method() default Method.GET;
|
||||
|
||||
/**
|
||||
* Indicates request processor should print "OK" to the HTTP client on success.
|
||||
*
|
||||
* <p>This is important because it's confusing to manually invoke a backend task and have a blank
|
||||
* page show up. And it's not worth injecting a {@link Response} object just to do something so
|
||||
* trivial.
|
||||
*/
|
||||
boolean automaticallyPrintOk() default false;
|
||||
|
||||
// TODO(b/26304887): Flip default to true.
|
||||
/** Enables XSRF protection on all HTTP methods except GET and HEAD. */
|
||||
boolean xsrfProtection() default false;
|
||||
|
||||
/** Arbitrary value included in the XSRF token hash. */
|
||||
String xsrfScope() default "app";
|
||||
|
||||
/**
|
||||
* Require user be logged-in or 302 redirect to the Google auth login page.
|
||||
*
|
||||
* <p><b>Warning:</b> DO NOT use this for cron and task queue endpoints.
|
||||
*
|
||||
* <p><b>Note:</b> Logged-in actions should also be guarded by a {@code <security-constraint>} in
|
||||
* {@code web.xml} with {@code <role-name>*</role-name>}.
|
||||
*/
|
||||
boolean requireLogin() default false;
|
||||
}
|
31
java/google/registry/request/Actions.java
Normal file
31
java/google/registry/request/Actions.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/** Helper methods for working with {@link Action @Action}-annotated classes. */
|
||||
public final class Actions {
|
||||
|
||||
/** Extracts the action path from the {@link Action @Action} annotation. */
|
||||
public static String getPathForAction(Class<?> action) {
|
||||
checkArgument(
|
||||
action.isAnnotationPresent(Action.class),
|
||||
"Cannot get path of class without @Action annotation: %s", action);
|
||||
return action.getAnnotation(Action.class).path();
|
||||
}
|
||||
|
||||
private Actions() {}
|
||||
}
|
48
java/google/registry/request/BUILD
Normal file
48
java/google/registry/request/BUILD
Normal file
|
@ -0,0 +1,48 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "request",
|
||||
srcs = glob(
|
||||
["*.java"],
|
||||
exclude = ["Modules.java"],
|
||||
),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/html",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/domain/registry/security",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/auto:auto_value",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/json_simple",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
],
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "modules",
|
||||
srcs = ["Modules.java"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":request",
|
||||
"//java/com/google/api/client/extensions/appengine/http",
|
||||
"//java/com/google/api/client/googleapis/auth/oauth2",
|
||||
"//java/com/google/api/client/googleapis/extensions/appengine/auth/oauth2",
|
||||
"//java/com/google/api/client/http",
|
||||
"//java/com/google/api/client/json",
|
||||
"//java/com/google/api/client/json/jackson2",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/keyring/api",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/dagger",
|
||||
],
|
||||
)
|
28
java/google/registry/request/DelegatedOAuthScopes.java
Normal file
28
java/google/registry/request/DelegatedOAuthScopes.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Dagger qualifier for the {@link Set} of OAuth2 scope strings used for authorization on APIs that
|
||||
* are connected to using a delegated user account (the serviceAccountUser in GoogleCredential).
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface DelegatedOAuthScopes {}
|
26
java/google/registry/request/Header.java
Normal file
26
java/google/registry/request/Header.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/** Dagger qualifier for HTTP headers. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface Header {
|
||||
String value();
|
||||
}
|
229
java/google/registry/request/HttpException.java
Normal file
229
java/google/registry/request/HttpException.java
Normal file
|
@ -0,0 +1,229 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.html.HtmlEscapers.htmlEscaper;
|
||||
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Base for exceptions that cause an HTTP error response. */
|
||||
public abstract class HttpException extends RuntimeException {
|
||||
|
||||
// as per https://tools.ietf.org/html/rfc4918
|
||||
private static final int SC_UNPROCESSABLE_ENTITY = 422;
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private final int responseCode;
|
||||
|
||||
protected HttpException(int responseCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.responseCode = responseCode;
|
||||
}
|
||||
|
||||
public final int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string associated with a particular response code. Unlike {@link #getMessage()},
|
||||
* which is meant to describe the circumstances under which this particular exception occurred,
|
||||
* this method always returns the same string (e.g. "Not Found" for a 400 error), and so should
|
||||
* always be the same for a given subclass of {@link HttpException}.
|
||||
*/
|
||||
public String getResponseCodeString() {
|
||||
return "Response Code " + responseCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmits the error response to the client.
|
||||
*
|
||||
* <p>{@link #getMessage()} will be sent to the browser, whereas {@link #toString()} and
|
||||
* {@link #getCause()} will be logged.
|
||||
*/
|
||||
public final void send(HttpServletResponse rsp) throws IOException {
|
||||
rsp.sendError(getResponseCode(), htmlEscaper().escape(getMessage()));
|
||||
logger.infofmt(getCause(), "%s", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception that causes a 204 response.
|
||||
*
|
||||
* <p>This is useful for App Engine task queue handlers that want to display an error, but don't
|
||||
* want the task to automatically retry, since the status code is less than 300.
|
||||
*/
|
||||
public static final class NoContentException extends HttpException {
|
||||
public NoContentException(String message) {
|
||||
super(HttpServletResponse.SC_NO_CONTENT, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "No Content";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 304 response. */
|
||||
public static final class NotModifiedException extends HttpException {
|
||||
public NotModifiedException() {
|
||||
this("Not Modified");
|
||||
}
|
||||
|
||||
public NotModifiedException(String message) {
|
||||
super(HttpServletResponse.SC_NOT_FOUND, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Not Modified";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 400 response. */
|
||||
public static final class BadRequestException extends HttpException {
|
||||
public BadRequestException(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public BadRequestException(String message, Throwable cause) {
|
||||
super(HttpServletResponse.SC_BAD_REQUEST, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Bad Request";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 403 response. */
|
||||
public static final class ForbiddenException extends HttpException {
|
||||
public ForbiddenException(String message) {
|
||||
super(HttpServletResponse.SC_FORBIDDEN, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Forbidden";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 404 response. */
|
||||
public static final class NotFoundException extends HttpException {
|
||||
public NotFoundException() {
|
||||
this("Not found");
|
||||
}
|
||||
|
||||
public NotFoundException(String message) {
|
||||
super(HttpServletResponse.SC_NOT_FOUND, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Not Found";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 405 response. */
|
||||
public static final class MethodNotAllowedException extends HttpException {
|
||||
public MethodNotAllowedException() {
|
||||
super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method not allowed", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Method Not Allowed";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 409 response. */
|
||||
public static final class ConflictException extends HttpException {
|
||||
public ConflictException(String message) {
|
||||
super(HttpServletResponse.SC_CONFLICT, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Conflict";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 415 response. */
|
||||
public static final class UnsupportedMediaTypeException extends HttpException {
|
||||
public UnsupportedMediaTypeException(String message) {
|
||||
super(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, null);
|
||||
}
|
||||
|
||||
public UnsupportedMediaTypeException(String message, Throwable cause) {
|
||||
super(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Unsupported Media Type";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 422 response. */
|
||||
public static final class UnprocessableEntityException extends HttpException {
|
||||
public UnprocessableEntityException(String message) {
|
||||
super(SC_UNPROCESSABLE_ENTITY, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Unprocessable Entity";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 500 response. */
|
||||
public static final class InternalServerErrorException extends HttpException {
|
||||
public InternalServerErrorException(String message) {
|
||||
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Internal Server Error";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 501 response. */
|
||||
public static final class NotImplementedException extends HttpException {
|
||||
public NotImplementedException(String message) {
|
||||
super(HttpServletResponse.SC_NOT_IMPLEMENTED, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Not Implemented";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception that causes a 503 response. */
|
||||
public static final class ServiceUnavailableException extends HttpException {
|
||||
public ServiceUnavailableException(String message) {
|
||||
super(HttpServletResponse.SC_SERVICE_UNAVAILABLE, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCodeString() {
|
||||
return "Service Unavailable";
|
||||
}
|
||||
}
|
||||
}
|
50
java/google/registry/request/JsonActionRunner.java
Normal file
50
java/google/registry/request/JsonActionRunner.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.base.Verify.verifyNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Runner for actions that read and write JSON objects. */
|
||||
public final class JsonActionRunner {
|
||||
|
||||
/** Interface for actions that read and write JSON objects. */
|
||||
public interface JsonAction {
|
||||
|
||||
/**
|
||||
* Handles JSON HTTP request.
|
||||
*
|
||||
* @param json object extracted from request body
|
||||
* @return an arbitrary JSON object, which is never {@code null}
|
||||
* @throws HttpException to send a non-200 status code / message to client
|
||||
*/
|
||||
Map<String, ?> handleJsonRequest(Map<String, ?> json);
|
||||
}
|
||||
|
||||
@Inject @JsonPayload Map<String, Object> payload;
|
||||
@Inject JsonResponse response;
|
||||
@Inject JsonActionRunner() {}
|
||||
|
||||
/** Delegates request to {@code action}. */
|
||||
public void run(JsonAction action) {
|
||||
response.setPayload(
|
||||
verifyNotNull(
|
||||
action.handleJsonRequest(payload),
|
||||
"handleJsonRequest() returned null"));
|
||||
}
|
||||
}
|
28
java/google/registry/request/JsonPayload.java
Normal file
28
java/google/registry/request/JsonPayload.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Dagger qualifier for the HTTP request payload as parsed JSON.
|
||||
*
|
||||
* @see RequestModule
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface JsonPayload {}
|
68
java/google/registry/request/JsonResponse.java
Normal file
68
java/google/registry/request/JsonResponse.java
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
|
||||
import static com.google.common.net.HttpHeaders.X_CONTENT_TYPE_OPTIONS;
|
||||
import static com.google.common.net.MediaType.JSON_UTF_8;
|
||||
import static org.json.simple.JSONValue.toJSONString;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** JSON response object. */
|
||||
public class JsonResponse {
|
||||
|
||||
/** String prefixed to all JSON-like responses to break {@code eval()}. */
|
||||
public static final String JSON_SAFETY_PREFIX = ")]}'\n";
|
||||
|
||||
protected final Response response;
|
||||
|
||||
@Inject
|
||||
public JsonResponse(Response rsp) {
|
||||
this.response = rsp;
|
||||
}
|
||||
|
||||
/** @see Response#setStatus */
|
||||
public void setStatus(int status) {
|
||||
response.setStatus(status);
|
||||
}
|
||||
|
||||
/** Writes the JSON map to the HTTP payload; call this exactly once. */
|
||||
public void setPayload(Map<String, ?> responseMap) {
|
||||
response.setContentType(JSON_UTF_8);
|
||||
// This prevents IE from MIME-sniffing a response away from the declared Content-Type.
|
||||
response.setHeader(X_CONTENT_TYPE_OPTIONS, "nosniff");
|
||||
// This is a defense in depth that prevents browsers from trying to render the content of the
|
||||
// response, even if all else fails. It's basically another anti-sniffing mechanism in the sense
|
||||
// that if you hit this url directly, it would try to download the file instead of showing it.
|
||||
response.setHeader(CONTENT_DISPOSITION, "attachment");
|
||||
response.setPayload(JSON_SAFETY_PREFIX + toJSONString(checkNotNull(responseMap)));
|
||||
}
|
||||
|
||||
/** @see Response#setHeader */
|
||||
public void setHeader(String header, String value) {
|
||||
response.setHeader(header, value);
|
||||
}
|
||||
|
||||
/** @see Response#setDateHeader */
|
||||
public void setDateHeader(String header, DateTime timestamp) {
|
||||
response.setDateHeader(header, timestamp);
|
||||
}
|
||||
}
|
202
java/google/registry/request/Modules.java
Normal file
202
java/google/registry/request/Modules.java
Normal file
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential;
|
||||
import com.google.api.client.http.HttpRequestInitializer;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.appengine.api.datastore.DatastoreService;
|
||||
import com.google.appengine.api.modules.ModulesService;
|
||||
import com.google.appengine.api.modules.ModulesServiceFactory;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Dagger modules for App Engine services and other vendor classes. */
|
||||
public final class Modules {
|
||||
|
||||
/** Dagger module for {@link DatastoreService}. */
|
||||
@Module
|
||||
public static final class DatastoreServiceModule {
|
||||
private static final DatastoreService datastoreService = getDatastoreService();
|
||||
|
||||
@Provides
|
||||
static DatastoreService provideDatastoreService() {
|
||||
return datastoreService;
|
||||
}
|
||||
}
|
||||
|
||||
/** Dagger module for {@link ModulesService}. */
|
||||
@Module
|
||||
public static final class ModulesServiceModule {
|
||||
private static final ModulesService modulesService = ModulesServiceFactory.getModulesService();
|
||||
|
||||
@Provides
|
||||
static ModulesService provideModulesService() {
|
||||
return modulesService;
|
||||
}
|
||||
}
|
||||
|
||||
/** Dagger module for {@link URLFetchService}. */
|
||||
@Module
|
||||
public static final class URLFetchServiceModule {
|
||||
private static final URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
|
||||
|
||||
@Provides
|
||||
static URLFetchService provideURLFetchService() {
|
||||
return fetchService;
|
||||
}
|
||||
}
|
||||
|
||||
/** Dagger module for {@link UserService}. */
|
||||
@Module
|
||||
public static final class UserServiceModule {
|
||||
private static final UserService userService = UserServiceFactory.getUserService();
|
||||
|
||||
@Provides
|
||||
static UserService provideUserService() {
|
||||
return userService;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dagger module that causes the Jackson2 JSON parser to be used for Google APIs requests.
|
||||
*
|
||||
* <p>Jackson1 and GSON can also satisfy the {@link JsonFactory} interface, but we've decided to
|
||||
* go with Jackson2, since it's what's used in the public examples for using Google APIs.
|
||||
*/
|
||||
@Module
|
||||
public static final class Jackson2Module {
|
||||
@Provides
|
||||
static JsonFactory provideJsonFactory() {
|
||||
return JacksonFactory.getDefaultInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/** Dagger module that causes the App Engine's URL fetcher to be used for Google APIs requests. */
|
||||
@Module
|
||||
public static final class UrlFetchTransportModule {
|
||||
private static final UrlFetchTransport HTTP_TRANSPORT = new UrlFetchTransport();
|
||||
|
||||
@Provides
|
||||
static HttpTransport provideHttpTransport() {
|
||||
return HTTP_TRANSPORT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dagger module providing {@link AppIdentityCredential}.
|
||||
*
|
||||
* <p>This can be used to authenticate to Google APIs using the identity of your GAE app.
|
||||
*
|
||||
* @see UseAppIdentityCredentialForGoogleApisModule
|
||||
*/
|
||||
@Module
|
||||
public static final class AppIdentityCredentialModule {
|
||||
@Provides
|
||||
static AppIdentityCredential provideAppIdentityCredential(@OAuthScopes Set<String> scopes) {
|
||||
return new AppIdentityCredential(scopes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dagger module causing Google APIs requests to be authorized with your GAE app identity.
|
||||
*
|
||||
* <p>You must also use the {@link AppIdentityCredential} module.
|
||||
*/
|
||||
@Module
|
||||
public static final class UseAppIdentityCredentialForGoogleApisModule {
|
||||
@Provides
|
||||
static HttpRequestInitializer provideHttpRequestInitializer(AppIdentityCredential credential) {
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dagger module providing {@link GoogleCredential} from a JSON key file contents.
|
||||
*
|
||||
* <p>This satisfies the {@link HttpRequestInitializer} interface for authenticating Google APIs
|
||||
* requests, just like {@link AppIdentityCredential}.
|
||||
*
|
||||
* <p>But we consider GAE authentication more desirable and easier to manage operations-wise. So
|
||||
* this authentication method should only be used for the following situations:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Locally-running programs (which aren't executing on the App Engine platform)
|
||||
* <li>Spreadsheet service (which can't use {@link AppIdentityCredential} due to an old library)
|
||||
* </ol>
|
||||
*
|
||||
* @see com.google.domain.registry.keyring.api.Keyring#getJsonCredential()
|
||||
*/
|
||||
@Module
|
||||
public static final class GoogleCredentialModule {
|
||||
|
||||
@Provides
|
||||
static GoogleCredential provideGoogleCredential(
|
||||
HttpTransport httpTransport,
|
||||
JsonFactory jsonFactory,
|
||||
@Key("jsonCredential") String jsonCredential) {
|
||||
try {
|
||||
return GoogleCredential.fromStream(
|
||||
new ByteArrayInputStream(jsonCredential.getBytes(UTF_8)), httpTransport, jsonFactory);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a GoogleCredential that will connect to GAE using delegated admin access. This is
|
||||
* needed for API calls requiring domain admin access to the relevant GAFYD using delegated
|
||||
* scopes, e.g. the Directory API and the Groupssettings API.
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("delegatedAdmin")
|
||||
static GoogleCredential provideDelegatedAdminGoogleCredential(
|
||||
GoogleCredential googleCredential,
|
||||
HttpTransport httpTransport,
|
||||
@DelegatedOAuthScopes Set<String> scopes,
|
||||
@Config("googleAppsAdminEmailAddress") String googleAppsAdminEmailAddress) {
|
||||
return new GoogleCredential.Builder()
|
||||
.setTransport(httpTransport)
|
||||
.setJsonFactory(googleCredential.getJsonFactory())
|
||||
.setServiceAccountId(googleCredential.getServiceAccountId())
|
||||
.setServiceAccountPrivateKey(googleCredential.getServiceAccountPrivateKey())
|
||||
.setServiceAccountScopes(scopes)
|
||||
.setServiceAccountUser(googleAppsAdminEmailAddress)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
25
java/google/registry/request/OAuthScopes.java
Normal file
25
java/google/registry/request/OAuthScopes.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/** Dagger qualifier for the {@link Set} of OAuth2 scope strings, used for API authorization. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface OAuthScopes {}
|
26
java/google/registry/request/Parameter.java
Normal file
26
java/google/registry/request/Parameter.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/** Dagger qualifier for HTTP parameters. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface Parameter {
|
||||
String value();
|
||||
}
|
24
java/google/registry/request/ParameterMap.java
Normal file
24
java/google/registry/request/ParameterMap.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/** Dagger qualifier for {@link com.google.common.collect.ImmutableListMultimap} of HTTP params. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface ParameterMap {}
|
28
java/google/registry/request/Payload.java
Normal file
28
java/google/registry/request/Payload.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Dagger qualifier for the HTTP request payload.
|
||||
*
|
||||
* @see RequestModule
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface Payload {}
|
159
java/google/registry/request/RequestHandler.java
Normal file
159
java/google/registry/request/RequestHandler.java
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static com.google.domain.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
|
||||
import static com.google.domain.registry.security.XsrfTokenManager.validateToken;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Dagger request processor for Domain Registry.
|
||||
*
|
||||
* <p>This class creates an HTTP request processor from a Dagger component. It routes requests from
|
||||
* your servlet to an {@link Action @Action} annotated handler class.
|
||||
*
|
||||
* <h3>Component Definition</h3>
|
||||
*
|
||||
* <p>Action instances are supplied on a per-request basis by invoking the methods on {@code C}.
|
||||
* For example:
|
||||
* <pre>
|
||||
* {@literal @Component}
|
||||
* interface ServerComponent {
|
||||
* HelloAction helloAction();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The rules for component methods are as follows:
|
||||
* <ol>
|
||||
* <li>Methods whose raw return type does not implement {@code Runnable} will be ignored
|
||||
* <li>Methods whose raw return type does not have an {@code @Action} annotation are ignored
|
||||
* </ol>
|
||||
*
|
||||
* <h3>Security Features</h3>
|
||||
*
|
||||
* <p>XSRF protection is built into this class. It can be enabled or disabled on individual actions
|
||||
* using {@link Action#xsrfProtection() xsrfProtection} setting.
|
||||
*
|
||||
* <p>This class also enforces the {@link Action#requireLogin() requireLogin} setting.
|
||||
*
|
||||
* @param <C> component type
|
||||
*/
|
||||
public final class RequestHandler<C> {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private static final Duration XSRF_VALIDITY = Duration.standardDays(1);
|
||||
|
||||
@NonFinalForTesting
|
||||
private static UserService userService = UserServiceFactory.getUserService();
|
||||
|
||||
/**
|
||||
* Creates a new request processor based off your component methods.
|
||||
*
|
||||
* <p><b>Warning:</b> When using the App Engine platform, you must call
|
||||
* {@link Method#setAccessible(boolean) setAccessible(true)} on all your component {@link Method}
|
||||
* instances, from within the same package as the component. This is due to cross-package
|
||||
* reflection restrictions.
|
||||
*
|
||||
* @param methods is the result of calling {@link Class#getMethods()} on {@code component}, which
|
||||
* are filtered to only include those with no arguments returning a {@link Runnable} with an
|
||||
* {@link Action} annotation
|
||||
*/
|
||||
public static <C> RequestHandler<C> create(Class<C> component, Iterable<Method> methods) {
|
||||
return new RequestHandler<>(component, Router.create(methods));
|
||||
}
|
||||
|
||||
private final Router router;
|
||||
|
||||
private RequestHandler(Class<C> component, Router router) {
|
||||
checkNotNull(component);
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the appropriate action for a servlet request.
|
||||
*
|
||||
* @param component is an instance of the component type whose methods were passed to
|
||||
* {@link #create(Class, Iterable)}
|
||||
*/
|
||||
public void handleRequest(HttpServletRequest req, HttpServletResponse rsp, C component)
|
||||
throws IOException {
|
||||
checkNotNull(component);
|
||||
checkNotNull(rsp);
|
||||
Action.Method method;
|
||||
try {
|
||||
method = Action.Method.valueOf(req.getMethod());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.infofmt("Unsupported method: %s", req.getMethod());
|
||||
rsp.sendError(SC_METHOD_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
String path = req.getRequestURI();
|
||||
Optional<Route> route = router.route(path);
|
||||
if (!route.isPresent()) {
|
||||
logger.infofmt("No action found for: %s", path);
|
||||
rsp.sendError(SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
if (!route.get().isMethodAllowed(method)) {
|
||||
logger.infofmt("Method %s not allowed for: %s", method, path);
|
||||
rsp.sendError(SC_METHOD_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
if (route.get().action().requireLogin() && !userService.isUserLoggedIn()) {
|
||||
logger.info("not logged in");
|
||||
rsp.setStatus(SC_MOVED_TEMPORARILY);
|
||||
rsp.setHeader(LOCATION, userService.createLoginURL(req.getRequestURI()));
|
||||
return;
|
||||
}
|
||||
if (route.get().shouldXsrfProtect(method)
|
||||
&& !validateToken(
|
||||
nullToEmpty(req.getHeader(X_CSRF_TOKEN)),
|
||||
route.get().action().xsrfScope(),
|
||||
XSRF_VALIDITY)) {
|
||||
rsp.sendError(SC_FORBIDDEN, "Invalid " + X_CSRF_TOKEN);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
route.get().instantiator().apply(component).run();
|
||||
if (route.get().action().automaticallyPrintOk()) {
|
||||
rsp.setContentType(PLAIN_TEXT_UTF_8.toString());
|
||||
rsp.getWriter().write("OK\n");
|
||||
}
|
||||
} catch (HttpException e) {
|
||||
e.send(rsp);
|
||||
}
|
||||
}
|
||||
}
|
28
java/google/registry/request/RequestMethod.java
Normal file
28
java/google/registry/request/RequestMethod.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Dagger qualifier for the HTTP request method.
|
||||
*
|
||||
* @see javax.servlet.http.HttpServletRequest#getMethod()
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface RequestMethod {}
|
154
java/google/registry/request/RequestModule.java
Normal file
154
java/google/registry/request/RequestModule.java
Normal file
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.net.MediaType.JSON_UTF_8;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.UnsupportedMediaTypeException;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import org.json.simple.JSONValue;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Dagger module for servlets. */
|
||||
@Module
|
||||
public final class RequestModule {
|
||||
|
||||
private final HttpServletRequest req;
|
||||
private final HttpServletResponse rsp;
|
||||
|
||||
public RequestModule(HttpServletRequest req, HttpServletResponse rsp) {
|
||||
this.req = req;
|
||||
this.rsp = rsp;
|
||||
}
|
||||
|
||||
@Provides
|
||||
static Response provideResponse(ResponseImpl response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Provides
|
||||
HttpServletRequest provideHttpServletRequest() {
|
||||
return req;
|
||||
}
|
||||
|
||||
@Provides
|
||||
HttpServletResponse provideHttpServletResponse() {
|
||||
return rsp;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RequestPath
|
||||
static String provideRequestPath(HttpServletRequest req) {
|
||||
return req.getRequestURI();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RequestMethod
|
||||
static Action.Method provideRequestMethod(HttpServletRequest req) {
|
||||
return Action.Method.valueOf(req.getMethod());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header("Content-Type")
|
||||
static MediaType provideContentType(HttpServletRequest req) {
|
||||
try {
|
||||
return MediaType.parse(req.getContentType());
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
throw new UnsupportedMediaTypeException("Bad Content-Type header", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Payload
|
||||
static String providePayloadAsString(HttpServletRequest req) {
|
||||
try {
|
||||
return CharStreams.toString(req.getReader());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Payload
|
||||
static byte[] providePayloadAsBytes(HttpServletRequest req) {
|
||||
try {
|
||||
return ByteStreams.toByteArray(req.getInputStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JsonPayload
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> provideJsonPayload(
|
||||
@Header("Content-Type") MediaType contentType,
|
||||
@Payload String payload) {
|
||||
if (!JSON_UTF_8.is(contentType.withCharset(UTF_8))) {
|
||||
throw new UnsupportedMediaTypeException(
|
||||
String.format("Expected %s Content-Type", JSON_UTF_8.withoutParameters()));
|
||||
}
|
||||
try {
|
||||
return (Map<String, Object>) JSONValue.parseWithException(payload);
|
||||
} catch (ParseException e) {
|
||||
throw new BadRequestException(
|
||||
"Malformed JSON", new VerifyException("Malformed JSON:\n" + payload, e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an immutable representation of the servlet request parameters.
|
||||
*
|
||||
* <p>This performs a shallow copy of the {@code Map<String, String[]>} data structure from the
|
||||
* servlets API, each time this is provided. This is almost certainly less expensive than the
|
||||
* thread synchronization expense of {@link javax.inject.Singleton @Singleton}.
|
||||
*
|
||||
* <p><b>Note:</b> If a parameter is specified without a value, e.g. {@code /foo?lol} then an
|
||||
* empty string value is assumed, since Guava's multimap doesn't permit {@code null} mappings.
|
||||
*
|
||||
* @see HttpServletRequest#getParameterMap()
|
||||
*/
|
||||
@Provides
|
||||
@ParameterMap
|
||||
static ImmutableListMultimap<String, String> provideParameterMap(HttpServletRequest req) {
|
||||
ImmutableListMultimap.Builder<String, String> params = new ImmutableListMultimap.Builder<>();
|
||||
@SuppressWarnings("unchecked") // Safe by specification.
|
||||
Map<String, String[]> original = req.getParameterMap();
|
||||
for (Map.Entry<String, String[]> param : original.entrySet()) {
|
||||
if (param.getValue().length == 0) {
|
||||
params.put(param.getKey(), "");
|
||||
} else {
|
||||
params.putAll(param.getKey(), param.getValue());
|
||||
}
|
||||
}
|
||||
return params.build();
|
||||
}
|
||||
}
|
201
java/google/registry/request/RequestParameters.java
Normal file
201
java/google/registry/request/RequestParameters.java
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Utilities for extracting parameters from HTTP requests. */
|
||||
public final class RequestParameters {
|
||||
|
||||
/** The standardized request parameter name used by any servlet that takes a tld parameter. */
|
||||
public static final String PARAM_TLD = "tld";
|
||||
|
||||
/**
|
||||
* Returns first GET or POST parameter associated with {@code name}.
|
||||
*
|
||||
* <p>For example, assume {@code name} is "bar". The following request URIs would cause this
|
||||
* method to yield the following results:
|
||||
*
|
||||
* <ul>
|
||||
* <li>/foo?bar=hello → hello
|
||||
* <li>/foo?bar=hello&bar=there → hello
|
||||
* <li>/foo?bar= → 400 error (empty)
|
||||
* <li>/foo?bar=&bar=there → 400 error (empty)
|
||||
* <li>/foo → 400 error (absent)
|
||||
* </ul>
|
||||
*
|
||||
* @throws BadRequestException if request parameter is absent or empty
|
||||
*/
|
||||
public static String extractRequiredParameter(HttpServletRequest req, String name) {
|
||||
String result = req.getParameter(name);
|
||||
if (isNullOrEmpty(result)) {
|
||||
throw new BadRequestException("Missing parameter: " + name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first GET or POST parameter associated with {@code name}.
|
||||
*
|
||||
* @throws BadRequestException if request parameter is absent (but not if empty)
|
||||
*/
|
||||
public static String extractRequiredMaybeEmptyParameter(HttpServletRequest req, String name) {
|
||||
String result = req.getParameter(name);
|
||||
if (result == null) {
|
||||
throw new BadRequestException("Missing parameter: " + name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the first GET or POST parameter associated with {@code name}. */
|
||||
public static Optional<String> extractOptionalParameter(HttpServletRequest req, String name) {
|
||||
return Optional.fromNullable(emptyToNull(req.getParameter(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first GET or POST parameter associated with {@code name} as an integer.
|
||||
*
|
||||
* @throws BadRequestException if request parameter is present but not a valid integer
|
||||
*/
|
||||
public static Optional<Integer> extractOptionalIntParameter(HttpServletRequest req, String name) {
|
||||
String stringParam = req.getParameter(name);
|
||||
try {
|
||||
return isNullOrEmpty(stringParam)
|
||||
? Optional.<Integer>absent()
|
||||
: Optional.of(Integer.valueOf(stringParam));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadRequestException("Expected integer: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first GET or POST parameter associated with {@code name} as an integer.
|
||||
*
|
||||
* @throws BadRequestException if request parameter is absent, empty, or not a valid integer
|
||||
*/
|
||||
public static int extractIntParameter(HttpServletRequest req, String name) {
|
||||
try {
|
||||
return Integer.valueOf(nullToEmpty(req.getParameter(name)));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadRequestException("Expected integer: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns all GET or POST parameters associated with {@code name}. */
|
||||
public static ImmutableSet<String> extractSetOfParameters(HttpServletRequest req, String name) {
|
||||
String[] parameters = req.getParameterValues(name);
|
||||
return parameters == null ? ImmutableSet.<String>of() : ImmutableSet.copyOf(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first GET or POST parameter associated with {@code name}.
|
||||
*
|
||||
* @throws BadRequestException if request parameter named {@code name} is absent, empty, or not
|
||||
* equal to any of the values in {@code enumClass}
|
||||
*/
|
||||
public static <C extends Enum<C>>
|
||||
C extractEnumParameter(HttpServletRequest req, Class<C> enumClass, String name) {
|
||||
try {
|
||||
return Enum.valueOf(enumClass, Ascii.toUpperCase(extractRequiredParameter(req, name)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(
|
||||
String.format("Invalid %s parameter: %s", enumClass.getSimpleName(), name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if parameter is present and not empty and not {@code "false"}.
|
||||
*
|
||||
* <p>This considers a parameter with a non-existent value true, for situations where the request
|
||||
* URI is something like {@code /foo?bar}, where the mere presence of the {@code bar} parameter
|
||||
* without a value indicates that it's true.
|
||||
*/
|
||||
public static boolean extractBooleanParameter(HttpServletRequest req, String name) {
|
||||
return req.getParameterMap().containsKey(name)
|
||||
&& !equalsFalse(req.getParameter(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first request parameter associated with {@code name} parsed as an
|
||||
* <a href="https://goo.gl/pk5Q2k">ISO 8601</a> timestamp, e.g. {@code 1984-12-18TZ},
|
||||
* {@code 2000-01-01T16:20:00Z}.
|
||||
*
|
||||
* @throws BadRequestException if request parameter named {@code name} is absent, empty, or could
|
||||
* not be parsed as an ISO 8601 timestamp
|
||||
*/
|
||||
public static DateTime extractRequiredDatetimeParameter(HttpServletRequest req, String name) {
|
||||
String stringValue = extractRequiredParameter(req, name);
|
||||
try {
|
||||
return DateTime.parse(stringValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("Bad ISO 8601 timestamp: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first request parameter associated with {@code name} parsed as an optional
|
||||
* {@link InetAddress} (which might be IPv6).
|
||||
*
|
||||
* @throws BadRequestException if request parameter named {@code name} is present but could not
|
||||
* be parsed as an {@link InetAddress}
|
||||
*/
|
||||
public static Optional<InetAddress> extractOptionalInetAddressParameter(
|
||||
HttpServletRequest req, String name) {
|
||||
Optional<String> paramVal = extractOptionalParameter(req, name);
|
||||
if (!paramVal.isPresent()) {
|
||||
return Optional.absent();
|
||||
}
|
||||
try {
|
||||
return Optional.of(InetAddresses.forString(paramVal.get()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("Not an IPv4 or IPv6 address: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean equalsFalse(@Nullable String value) {
|
||||
return nullToEmpty(value).equalsIgnoreCase("false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first HTTP header associated with {@code name}.
|
||||
*
|
||||
* @param name case insensitive header name
|
||||
* @throws BadRequestException if request header is absent or empty
|
||||
*/
|
||||
public static String extractRequiredHeader(HttpServletRequest req, String name) {
|
||||
String result = req.getHeader(name);
|
||||
if (isNullOrEmpty(result)) {
|
||||
throw new BadRequestException("Missing header: " + name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private RequestParameters() {}
|
||||
}
|
28
java/google/registry/request/RequestPath.java
Normal file
28
java/google/registry/request/RequestPath.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Dagger qualifier for the HTTP request path.
|
||||
*
|
||||
* @see javax.servlet.http.HttpServletRequest#getRequestURI()
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface RequestPath {}
|
30
java/google/registry/request/RequestScope.java
Normal file
30
java/google/registry/request/RequestScope.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.inject.Scope;
|
||||
|
||||
/** Dagger annotation for request-scoped components that depend on a global component. */
|
||||
@Scope
|
||||
@Documented
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequestScope {}
|
64
java/google/registry/request/Response.java
Normal file
64
java/google/registry/request/Response.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import com.google.common.net.MediaType;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* HTTP request response object.
|
||||
*
|
||||
* @see ResponseImpl
|
||||
*/
|
||||
public interface Response {
|
||||
|
||||
/** Sets the HTTP status code. */
|
||||
void setStatus(int status);
|
||||
|
||||
/** Sets the HTTP Content-Type and possibly encoding. */
|
||||
void setContentType(MediaType contentType);
|
||||
|
||||
/**
|
||||
* Writes the HTTP payload.
|
||||
*
|
||||
* @throws IllegalStateException if you've already written the payload
|
||||
*/
|
||||
void setPayload(String payload);
|
||||
|
||||
/**
|
||||
* Writes an HTTP header to the response.
|
||||
*
|
||||
* @see HttpServletResponse#setHeader(String, String)
|
||||
*/
|
||||
void setHeader(String header, String value);
|
||||
|
||||
/**
|
||||
* Writes an HTTP header with a timestamp value.
|
||||
*
|
||||
* @see HttpServletResponse#setDateHeader(String, long)
|
||||
*/
|
||||
void setDateHeader(String header, DateTime timestamp);
|
||||
|
||||
/**
|
||||
* Sends a JavaScript redirect HTTP response.
|
||||
*
|
||||
* GAE handles a HTTP 302 status as an error, so using this is helpful for responses that might
|
||||
* sometimes be consumed by GAE code, since it performs a redirect while also returning HTTP 200.
|
||||
*/
|
||||
void sendJavaScriptRedirect(String redirectUrl);
|
||||
}
|
73
java/google/registry/request/ResponseImpl.java
Normal file
73
java/google/registry/request/ResponseImpl.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import com.google.common.net.MediaType;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** HTTP response object. */
|
||||
public final class ResponseImpl implements Response {
|
||||
|
||||
/** Code for a JavaScript redirect. */
|
||||
private static final String REDIRECT_PAYLOAD_FORMAT =
|
||||
"<script>window.location.replace(\"%1$s\");</script><a href=\"%1$s\">%1$s</a>";
|
||||
|
||||
private final HttpServletResponse rsp;
|
||||
|
||||
@Inject
|
||||
public ResponseImpl(HttpServletResponse rsp) {
|
||||
this.rsp = rsp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int status) {
|
||||
rsp.setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(MediaType contentType) {
|
||||
rsp.setContentType(contentType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPayload(String payload) {
|
||||
try {
|
||||
rsp.getWriter().write(payload);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String header, String value) {
|
||||
rsp.setHeader(header, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDateHeader(String header, DateTime timestamp) {
|
||||
rsp.setDateHeader(header, timestamp.getMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendJavaScriptRedirect(String redirectUrl) {
|
||||
setPayload(String.format(REDIRECT_PAYLOAD_FORMAT, redirectUrl));
|
||||
}
|
||||
}
|
47
java/google/registry/request/Route.java
Normal file
47
java/google/registry/request/Route.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* Mapping of an {@link Action} to a {@link Runnable} instantiator for request handling.
|
||||
*
|
||||
* @see Router
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class Route {
|
||||
|
||||
static Route create(Action action, Function<Object, Runnable> instantiator) {
|
||||
return new AutoValue_Route(action, instantiator);
|
||||
}
|
||||
|
||||
abstract Action action();
|
||||
abstract Function<Object, Runnable> instantiator();
|
||||
|
||||
boolean isMethodAllowed(Action.Method requestMethod) {
|
||||
for (Action.Method method : action().method()) {
|
||||
if (method == requestMethod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldXsrfProtect(Action.Method requestMethod) {
|
||||
return action().xsrfProtection() && requestMethod != Action.Method.GET;
|
||||
}
|
||||
}
|
107
java/google/registry/request/Router.java
Normal file
107
java/google/registry/request/Router.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Path prefix request router for Domain Registry.
|
||||
*
|
||||
* <p>See the documentation of {@link RequestHandler} for more information.
|
||||
*
|
||||
* <h3>Implementation Details</h3>
|
||||
*
|
||||
* <p>Request routing is O(logn) because {@link ImmutableSortedMap} performs a binary search over a
|
||||
* contiguous array, which makes it faster than a {@link TreeMap}. However a prefix trie search in
|
||||
* generated code would be the ideal approach.
|
||||
*/
|
||||
final class Router {
|
||||
|
||||
static Router create(Iterable<Method> componentMethods) {
|
||||
return new Router(extractRoutesFromComponent(componentMethods));
|
||||
}
|
||||
|
||||
private final ImmutableSortedMap<String, Route> routes;
|
||||
|
||||
private Router(ImmutableSortedMap<String, Route> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
/** Returns the appropriate action route for a request. */
|
||||
Optional<Route> route(String path) {
|
||||
Map.Entry<String, Route> floor = routes.floorEntry(path);
|
||||
if (floor != null) {
|
||||
if (floor.getValue().action().isPrefix()
|
||||
? path.startsWith(floor.getKey())
|
||||
: path.equals(floor.getKey())) {
|
||||
return Optional.of(floor.getValue());
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
private static
|
||||
ImmutableSortedMap<String, Route> extractRoutesFromComponent(Iterable<Method> methods) {
|
||||
ImmutableSortedMap.Builder<String, Route> routes =
|
||||
new ImmutableSortedMap.Builder<>(Ordering.natural());
|
||||
for (Method method : methods) {
|
||||
if (!isDaggerInstantiatorOfType(Runnable.class, method)) {
|
||||
continue;
|
||||
}
|
||||
Action action = method.getReturnType().getAnnotation(Action.class);
|
||||
if (action == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked") // Safe due to previous checks.
|
||||
Route route =
|
||||
Route.create(action, (Function<Object, Runnable>) newInstantiator(method));
|
||||
routes.put(action.path(), route);
|
||||
}
|
||||
return routes.build();
|
||||
}
|
||||
|
||||
private static boolean isDaggerInstantiatorOfType(Class<?> type, Method method) {
|
||||
return method.getParameterTypes().length == 0
|
||||
&& type.isAssignableFrom(method.getReturnType());
|
||||
}
|
||||
|
||||
private static Function<Object, ?> newInstantiator(final Method method) {
|
||||
return new Function<Object, Object>() {
|
||||
@Override
|
||||
public Object apply(Object component) {
|
||||
try {
|
||||
return method.invoke(component);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(
|
||||
"Error reflectively accessing component's @Action factory method", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
// This means an exception was thrown during the injection process while instantiating
|
||||
// the @Action class; we should propagate that underlying exception.
|
||||
Throwables.propagateIfPossible(e.getCause());
|
||||
throw new AssertionError(
|
||||
"Component's @Action factory method somehow threw checked exception", e);
|
||||
}
|
||||
}};
|
||||
}
|
||||
}
|
51
java/google/registry/request/ServletDelegate.java
Normal file
51
java/google/registry/request/ServletDelegate.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.request;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Action that delegates to a servlet.
|
||||
*
|
||||
* <p>This is a transitory solution for using legacy servlets inside the new injection action
|
||||
* framework.
|
||||
*
|
||||
* @param <T> servlet type, which must have an {@link Inject @Inject} constructor
|
||||
*/
|
||||
public abstract class ServletDelegate<T extends HttpServlet> implements Runnable {
|
||||
|
||||
@Inject T servlet;
|
||||
@Inject HttpServletRequest req;
|
||||
@Inject HttpServletResponse rsp;
|
||||
|
||||
protected ServletDelegate() {}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
try {
|
||||
servlet.init();
|
||||
servlet.service(req, rsp);
|
||||
servlet.destroy();
|
||||
} catch (ServletException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
16
java/google/registry/request/package-info.java
Normal file
16
java/google/registry/request/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.request;
|
Loading…
Add table
Add a link
Reference in a new issue