/*
* Copyright 2013 cruxframework.org.
*
* 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 org.cruxframework.crux.core.server.rest.spi;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.cruxframework.crux.core.server.rest.core.Cookie;
import org.cruxframework.crux.core.server.rest.core.EntityTag;
import org.cruxframework.crux.core.server.rest.core.Headers;
import org.cruxframework.crux.core.server.rest.core.HttpHeaders;
import org.cruxframework.crux.core.server.rest.core.MediaType;
import org.cruxframework.crux.core.server.rest.core.MultivaluedMap;
import org.cruxframework.crux.core.server.rest.core.UriBuilder;
import org.cruxframework.crux.core.server.rest.core.dispatch.CacheInfo;
import org.cruxframework.crux.core.server.rest.core.dispatch.ConditionalResponse;
import org.cruxframework.crux.core.server.rest.core.dispatch.ResourceMethod.MethodReturn;
import org.cruxframework.crux.core.server.rest.util.HttpHeaderNames;
import org.cruxframework.crux.core.server.rest.util.HttpResponseCodes;
import org.cruxframework.crux.core.server.rest.util.MediaTypeHelper;
import org.cruxframework.crux.core.server.rest.util.PathHelper;
import org.cruxframework.crux.core.server.rest.util.header.MediaTypeHeaderParser;
/**
*
* @author Thiago da Rosa de Bustamante
*
*/
public class HttpUtil
{
public static UriInfo extractUriInfo(HttpServletRequest request)
{
String servletPrefix = request.getServletPath();
String contextPath = request.getContextPath();
if (servletPrefix != null && servletPrefix.length() > 0 && !servletPrefix.equals("/"))
{
if (!contextPath.endsWith("/") && !servletPrefix.startsWith("/"))
{
contextPath += "/";
}
contextPath += servletPrefix;
}
URI absolutePath = null;
try
{
URL absolute = new URL(request.getRequestURL().toString());
UriBuilder builder = new UriBuilder();
builder.scheme(absolute.getProtocol());
builder.host(absolute.getHost());
builder.port(absolute.getPort());
builder.path(absolute.getPath());
builder.replaceQuery(null);
absolutePath = builder.build();
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
String path = PathHelper.getEncodedPathInfo(absolutePath.getRawPath(), contextPath);
URI relativeURI = UriBuilder.fromUri(path).replaceQuery(request.getQueryString()).build();
URI baseURI = absolutePath;
if (!path.trim().equals(""))
{
String tmpContextPath = contextPath;
if (!tmpContextPath.endsWith("/"))
{
tmpContextPath += "/";
}
baseURI = UriBuilder.fromUri(absolutePath).replacePath(tmpContextPath).build();
}
UriInfo uriInfo = new UriInfo(baseURI, relativeURI);
return uriInfo;
}
public static HttpHeaders extractHttpHeaders(HttpServletRequest request)
{
HttpHeaders headers = new HttpHeaders();
MultivaluedMap<String, String> requestHeaders = extractRequestHeaders(request);
headers.setRequestHeaders(requestHeaders);
List<MediaType> acceptableMediaTypes = extractAccepts(requestHeaders);
List<String> acceptableLanguages = extractLanguages(requestHeaders);
headers.setAcceptableMediaTypes(acceptableMediaTypes);
headers.setAcceptableLanguages(acceptableLanguages);
headers.setLanguage(requestHeaders.getFirst(HttpHeaderNames.CONTENT_LANGUAGE));
String contentType = request.getContentType();
if (contentType != null)
{
headers.setMediaType(MediaType.valueOf(contentType));
}
Map<String, Cookie> cookies = extractCookies(request);
headers.setCookies(cookies);
return headers;
}
public static String wGet(String targetURL, String urlParameters, String method, String locale)
{
URL url;
HttpURLConnection connection = null;
try {
//Create connection
if(method != null && method.equals("GET") && !StringUtils.isEmpty(urlParameters))
{
targetURL += "?" + urlParameters;
}
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", locale);
connection.setUseCaches(false);
connection.setDoInput(true);
//Send request
if(method != null && method.equals("POST"))
{
DataOutputStream wr = new DataOutputStream (
connection.getOutputStream ());
connection.setDoOutput(true);
wr.writeBytes (urlParameters);
wr.flush ();
wr.close ();
}
//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e)
{
e.printStackTrace();
return null;
} finally
{
if(connection != null)
{
connection.disconnect();
}
}
}
static Map<String, Cookie> extractCookies(HttpServletRequest request)
{
Map<String, Cookie> cookies = new HashMap<String, Cookie>();
if (request.getCookies() != null)
{
for (javax.servlet.http.Cookie cookie : request.getCookies())
{
cookies.put(cookie.getName(), new Cookie(cookie.getName(), cookie.getValue(), cookie.getPath(), cookie.getDomain(), cookie.getVersion()));
}
}
return cookies;
}
public static List<MediaType> extractAccepts(MultivaluedMap<String, String> requestHeaders)
{
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
List<String> accepts = requestHeaders.get(HttpHeaderNames.ACCEPT);
if (accepts == null)
{
return acceptableMediaTypes;
}
for (String accept : accepts)
{
acceptableMediaTypes.addAll(MediaTypeHelper.parseHeader(accept));
}
return acceptableMediaTypes;
}
public static List<String> extractLanguages(MultivaluedMap<String, String> requestHeaders)
{
List<String> acceptable = new ArrayList<String>();
List<String> accepts = requestHeaders.get(HttpHeaderNames.ACCEPT_LANGUAGE);
if (accepts == null)
{
return acceptable;
}
for (String accept : accepts)
{
String[] splits = accept.split(",");
for (String split : splits)
{
acceptable.add(split.trim());
}
}
return acceptable;
}
@SuppressWarnings("unchecked")
public static MultivaluedMap<String, String> extractRequestHeaders(HttpServletRequest request)
{
Headers<String> requestHeaders = new Headers<String>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements())
{
String headerName = headerNames.nextElement();
Enumeration<String> headerValues = request.getHeaders(headerName);
while (headerValues.hasMoreElements())
{
String headerValue = headerValues.nextElement();
// System.out.println("ADDING HEADER: " + headerName +
// " value: " + headerValue);
requestHeaders.add(headerName, headerValue);
}
}
return requestHeaders;
}
public static boolean acceptsGzipEncoding(HttpRequest request)
{
assert (request != null);
String acceptEncoding = request.getHttpHeaders().getHeaderString(HttpHeaderNames.ACCEPT_ENCODING);
if (null == acceptEncoding)
{
return false;
}
return (acceptEncoding.indexOf("gzip") != -1);
}
private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
private static boolean exceedsUncompressedContentLengthLimit(String content)
{
return (content != null) && ((content.length() * 2) > UNCOMPRESSED_BYTE_SIZE_LIMIT);
}
public static boolean shouldGzipResponseContent(HttpRequest request, String responseContent)
{
return acceptsGzipEncoding(request) && exceedsUncompressedContentLengthLimit(responseContent);
}
public static void writeResponse(HttpRequest request, HttpResponse response, MethodReturn methodReturn) throws IOException
{
HttpServletResponseHeaders outputHeaders = response.getOutputHeaders();
if (methodReturn == null)
{
response.sendEmptyResponse();
}
else if (methodReturn.getCheckedExceptionData() != null)
{
response.sendError(HttpResponseCodes.SC_FORBIDDEN, methodReturn.getCheckedExceptionData());
}
else if (!methodReturn.hasReturnType())
{
response.setContentLength(0);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
else if (methodReturn.getConditionalResponse() != null)
{
writeConditionalResponse(response, methodReturn, outputHeaders);
}
else
{
CacheInfo cacheInfo = methodReturn.getCacheInfo();
if (cacheInfo != null)
{
writeCacheHeaders(response, cacheInfo, methodReturn.getEtag(), methodReturn.getDateModified(), methodReturn.isEtagGenerationEnabled());
}
String responseContent = methodReturn.getReturn();
byte[] responseBytes = getResponseBytes(request, response, responseContent);
response.setContentLength(responseBytes.length);
response.setStatus(HttpServletResponse.SC_OK);
outputHeaders.putSingle(HttpHeaderNames.CONTENT_TYPE, new MediaType("application", "json", "UTF-8"));
response.getOutputStream().write(responseBytes);
}
}
private static void writeConditionalResponse(HttpResponse response, MethodReturn methodReturn, HttpServletResponseHeaders outputHeaders)
{
ConditionalResponse conditionalResponse = methodReturn.getConditionalResponse();
response.setStatus(conditionalResponse.getStatus());
EntityTag etag = conditionalResponse.getEtag();
long dateModified = conditionalResponse.getLastModified();
CacheInfo cacheInfo = methodReturn.getCacheInfo();
if (cacheInfo != null)
{
writeCacheHeaders(response, cacheInfo, etag, dateModified, methodReturn.isEtagGenerationEnabled());
}
else
{
//Confirmar se devo mandar etag e last modified em 412 ou 304 para escritas
if (etag != null)
{
outputHeaders.putSingle(HttpHeaderNames.ETAG, etag);
}
if (dateModified > 0)
{
outputHeaders.addDateHeader(HttpHeaderNames.LAST_MODIFIED, dateModified);
}
}
}
private static void writeCacheHeaders(HttpResponse response, CacheInfo cacheInfo, EntityTag etag, long dateModified, boolean forceEtagGeneration)
{
org.cruxframework.crux.core.server.rest.core.CacheControl cacheControl = new org.cruxframework.crux.core.server.rest.core.CacheControl();
HttpServletResponseHeaders outputHeaders = response.getOutputHeaders();
if (!cacheInfo.isCacheEnabled())
{
cacheControl.setNoStore(true);
outputHeaders.addDateHeader(HttpHeaderNames.EXPIRES, 0);
if (forceEtagGeneration && etag != null)
{
outputHeaders.putSingle(HttpHeaderNames.ETAG, etag);
}
}
else
{
outputHeaders.add(HttpHeaderNames.VARY, HttpHeaderNames.ACCEPT_LANGUAGE);
long expires = cacheInfo.defineExpires();
outputHeaders.addDateHeader(HttpHeaderNames.EXPIRES, expires);
if (etag != null)
{
outputHeaders.putSingle(HttpHeaderNames.ETAG, etag);
}
if (dateModified > 0)
{
outputHeaders.addDateHeader(HttpHeaderNames.LAST_MODIFIED, dateModified);
}
switch (cacheInfo.getCacheControl())
{
case PUBLIC:
cacheControl.setPublic(true);
cacheControl.setMaxAge(cacheInfo.getCacheTime());
break;
case PRIVATE:
cacheControl.setPrivate(true);
cacheControl.setMaxAge(cacheInfo.getCacheTime());
break;
case NO_CACHE:
cacheControl.setNoCache(true);
break;
}
cacheControl.setNoTransform(cacheInfo.isNoTransform());
cacheControl.setMustRevalidate(cacheInfo.isMustRevalidate());
cacheControl.setProxyRevalidate(cacheInfo.isProxyRevalidate());
}
outputHeaders.putSingle(HttpHeaderNames.CACHE_CONTROL, cacheControl);
}
private static byte[] getResponseBytes(HttpRequest request, HttpResponse response, String responseContent) throws UnsupportedEncodingException, IOException
{
boolean gzipResponse = shouldGzipResponseContent(request, responseContent);
byte[] responseBytes = (responseContent!=null?responseContent.getBytes("UTF-8"):new byte[0]);
if (gzipResponse)
{
ByteArrayOutputStream output = null;
GZIPOutputStream gzipOutputStream = null;
try
{
output = new ByteArrayOutputStream(responseBytes.length);
gzipOutputStream = new GZIPOutputStream(output);
gzipOutputStream.write(responseBytes);
gzipOutputStream.finish();
gzipOutputStream.flush();
response.getOutputHeaders().putSingle(HttpHeaderNames.CONTENT_ENCODING, "gzip");
responseBytes = output.toByteArray();
}
catch (IOException e)
{
throw new InternalServerErrorException("Unable to compress response", "Error processing requested service", e);
}
finally
{
if (null != gzipOutputStream)
{
gzipOutputStream.close();
}
if (null != output)
{
output.close();
}
}
}
return responseBytes;
}
public static void sendError(HttpServletResponse response, int status, String message) throws IOException
{
response.setStatus(status);
if (message!= null)
{
byte[] responseBytes = HttpResponse.serializeException(message).getBytes("UTF-8");
response.setContentLength(responseBytes.length);
response.setHeader(HttpHeaderNames.CONTENT_TYPE, MediaTypeHeaderParser.toString(new MediaType("text", "plain", "UTF-8")));
response.getOutputStream().write(responseBytes);
}
}
}