com.squareup.rack.servlet.RackEnvironmentBuilder Maven / Gradle / Ivy
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.squareup.rack.servlet;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.squareup.rack.RackEnvironment;
import com.squareup.rack.RackErrors;
import com.squareup.rack.RackInput;
import com.squareup.rack.RackLogger;
import com.squareup.rack.io.TempfileBufferedInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.Throwables.propagate;
import static com.squareup.rack.RackEnvironment.CONTENT_LENGTH;
import static com.squareup.rack.RackEnvironment.CONTENT_TYPE;
import static com.squareup.rack.RackEnvironment.HTTP_HEADER_PREFIX;
import static com.squareup.rack.RackEnvironment.MINECART_HTTP_SERVLET_REQUEST;
import static com.squareup.rack.RackEnvironment.PATH_INFO;
import static com.squareup.rack.RackEnvironment.QUERY_STRING;
import static com.squareup.rack.RackEnvironment.RACK_ERRORS;
import static com.squareup.rack.RackEnvironment.RACK_HIJACK;
import static com.squareup.rack.RackEnvironment.RACK_INPUT;
import static com.squareup.rack.RackEnvironment.RACK_LOGGER;
import static com.squareup.rack.RackEnvironment.RACK_MULTIPROCESS;
import static com.squareup.rack.RackEnvironment.RACK_MULTITHREAD;
import static com.squareup.rack.RackEnvironment.RACK_RUN_ONCE;
import static com.squareup.rack.RackEnvironment.RACK_URL_SCHEME;
import static com.squareup.rack.RackEnvironment.RACK_VERSION;
import static com.squareup.rack.RackEnvironment.REQUEST_METHOD;
import static com.squareup.rack.RackEnvironment.SCRIPT_NAME;
import static com.squareup.rack.RackEnvironment.SERVER_NAME;
import static com.squareup.rack.RackEnvironment.SERVER_PORT;
import static java.util.Collections.list;
/**
* Transforms an {@link HttpServletRequest} into a {@link RackEnvironment}.
*
* Conforms to version 1.2 of the Rack specification
.
*
* @see The Rack Specification
* @see RFC 3875, section 4.1.18
* @see The Rack
* socket hijacking API
*/
public class RackEnvironmentBuilder {
// We conform to version 1.2 of the Rack specification.
// Note that this number is completely different than the gem version of rack (lowercase):
// for example, the rack-1.5.2 gem ships with handlers that conform to version 1.2 of the Rack
// specification.
private static final List VERSION_1_2 = ImmutableList.of(1, 2);
private static final Logger RACK_ERRORS_LOGGER = LoggerFactory.getLogger(RackErrors.class);
private static final Logger RACK_LOGGER_LOGGER = LoggerFactory.getLogger(RackLogger.class);
private static final Joiner COMMA = Joiner.on(',');
private static final CharMatcher DASH = CharMatcher.is('-');
public RackEnvironment build(HttpServletRequest request) {
ImmutableMap.Builder content = ImmutableMap.builder();
content.put(REQUEST_METHOD, request.getMethod());
content.put(SCRIPT_NAME, request.getServletPath());
content.put(PATH_INFO, nullToEmpty(request.getPathInfo()));
content.put(QUERY_STRING, nullToEmpty(request.getQueryString()));
content.put(SERVER_NAME, request.getServerName());
content.put(SERVER_PORT, String.valueOf(request.getServerPort()));
content.put(RACK_VERSION, VERSION_1_2);
content.put(RACK_URL_SCHEME, request.getScheme().toLowerCase());
content.put(RACK_INPUT, rackInput(request));
content.put(RACK_ERRORS, new RackErrors(RACK_ERRORS_LOGGER));
content.put(RACK_LOGGER, new RackLogger(RACK_LOGGER_LOGGER));
content.put(RACK_MULTITHREAD, true);
content.put(RACK_MULTIPROCESS, true);
content.put(RACK_RUN_ONCE, false);
content.put(RACK_HIJACK, false);
// Extra things we add that aren't in the Rack specification:
content.put(MINECART_HTTP_SERVLET_REQUEST, request);
// HTTP headers; Multimap acrobatics ensure we normalize capitalization and
// punctuation differences early
Enumeration headerNames = request.getHeaderNames();
ImmutableListMultimap.Builder headers = ImmutableListMultimap.builder();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
headers.putAll(rackHttpHeaderKey(name), list(request.getHeaders(name)));
}
for (Map.Entry> header : headers.build().asMap().entrySet()) {
content.put(header.getKey(), COMMA.join(header.getValue()));
}
// Request attributes
// This will include attributes like javax.servlet.request.X509Certificate
Enumeration attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
content.put(name, request.getAttribute(name));
}
return new RackEnvironment(content.build());
}
private RackInput rackInput(HttpServletRequest request) {
try {
return new RackInput(new TempfileBufferedInputStream(request.getInputStream()));
} catch (IOException e) {
throw propagate(e);
}
}
private String rackHttpHeaderKey(String headerName) {
String transformed = DASH.replaceFrom(headerName.toUpperCase(), "_");
if (transformed.equals(CONTENT_LENGTH) || transformed.equals(CONTENT_TYPE)) {
return transformed;
} else {
return HTTP_HEADER_PREFIX + transformed;
}
}
}