/*
* Copyright 2011-2012 M3, 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.m3.curly;
import jsr166y.ForkJoinPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HTTP
*/
public class HTTP {
private static final Logger logger = LoggerFactory.getLogger(HTTP.class);
private static final ForkJoinPool forkJoinPool = new ForkJoinPool();
private HTTP() {
}
public static Response get(Request request) throws IOException {
return request(Method.GET, request);
}
public static Future<Response> asyncGet(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() {
try {
Response response = get(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response get(String url) throws IOException {
return get(new Request(url));
}
public static Future<Response> asyncGet(String url) {
return asyncGet(new AsyncRequest(url));
}
public static Response get(String url, String charset) throws IOException {
return get(new Request(url, charset));
}
public static Future<Response> asyncGet(String url, String charset) {
return asyncGet(new AsyncRequest(url, charset));
}
public static Response post(Request request) throws IOException {
return request(Method.POST, request);
}
public static Future<Response> asyncPost(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = post(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response post(String url, Map<String, ?> formParams) throws IOException {
return post(new Request(url, formParams));
}
public static Future<Response> asyncPost(String url, Map<String, ?> formParams) {
return asyncPost(new AsyncRequest(url, formParams));
}
public static Response post(String url, List<? extends FormData> multipartFormData) throws IOException {
Request request = new Request(url);
request.setMultipartFormData(multipartFormData);
return post(request);
}
public static Future<Response> asyncPost(String url, List<? extends FormData> multipartFormData) {
AsyncRequest request = new AsyncRequest(url);
request.setMultipartFormData(multipartFormData);
return asyncPost(request);
}
public static Response post(String url, byte[] body, String contentType) throws IOException {
Request request = new Request(url);
request.setBody(body, contentType);
return post(request);
}
public static Future<Response> asyncPost(String url, byte[] body, String contentType) {
AsyncRequest request = new AsyncRequest(url);
request.setBody(body, contentType);
return asyncPost(request);
}
public static Response put(Request request) throws IOException {
return request(Method.PUT, request);
}
public static Future<Response> asyncPut(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = put(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response put(String url, Map<String, ?> formParams) throws IOException {
return put(new Request(url, formParams));
}
public static Future<Response> asyncPut(String url, Map<String, ?> formParams) {
return asyncPut(new AsyncRequest(url, formParams));
}
public static Response put(String url, List<? extends FormData> multipartFormData) throws IOException {
Request request = new Request(url);
request.setMultipartFormData(multipartFormData);
return put(request);
}
public static Future<Response> asyncPut(String url, List<? extends FormData> multipartFormData) {
AsyncRequest request = new AsyncRequest(url);
request.setMultipartFormData(multipartFormData);
return asyncPut(request);
}
public static Response put(String url, byte[] body, String contentType) throws IOException {
Request request = new Request(url);
request.setBody(body, contentType);
return put(request);
}
public static Future<Response> asyncPut(String url, byte[] body, String contentType) {
AsyncRequest request = new AsyncRequest(url);
request.setBody(body, contentType);
return asyncPut(request);
}
public static Response delete(Request request) throws IOException {
return request(Method.DELETE, request);
}
public static Future<Response> asyncDelete(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = delete(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response delete(String url) throws IOException {
return delete(new Request(url));
}
public static Future<Response> asyncDelete(final String url) {
return asyncDelete(new AsyncRequest(url));
}
public static Response head(Request request) throws IOException {
return request(Method.HEAD, request);
}
public static Future<Response> asyncHead(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = head(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response head(String url) throws IOException {
return head(new Request(url));
}
public static Future<Response> asyncHead(final String url) {
return asyncHead(new AsyncRequest(url));
}
public static Response options(Request request) throws IOException {
return request(Method.OPTIONS, request);
}
public static Future<Response> asyncOptions(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = options(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response options(String url) throws IOException {
return options(new Request(url));
}
public static Future<Response> asyncOptions(final String url) {
return asyncOptions(new AsyncRequest(url));
}
public static Response trace(Request request) throws IOException {
return request(Method.TRACE, request);
}
public static Future<Response> asyncTrace(final AsyncRequest request) {
return executeAndReturn(new FutureTask(new Callable<Response>() {
public Response call() throws IOException {
try {
Response response = trace(request);
request.onSuccess(response);
return response;
} catch (IOException e) {
request.onFailure(e);
}
return null;
}
}));
}
public static Response trace(String url) throws IOException {
return trace(new Request(url));
}
public static Future<Response> asyncTrace(final String url) {
return asyncTrace(new AsyncRequest(url));
}
public static Response request(Method method, Request request) throws IOException {
if (logger.isDebugEnabled()) {
StringBuilder requestInfo = new StringBuilder().append("\n")
.append("- HTTP Request started. -\n")
.append(" " + method + " " + request.getUrl() + "\n")
.append(" Charset: " + request.getCharset() + "\n")
.append(" Content-Type: " + request.getContentType() + "\n")
.append(" Referer: " + request.getReferer() + "\n")
.append(" User-Agent: " + request.getUserAgent() + "\n");
for (String name : request.getHeaderNames()) {
requestInfo.append(" " + name + ": " + request.getHeader(name) + "\n");
}
requestInfo.append("---------\n");
logger.debug(requestInfo.toString());
}
HttpURLConnection conn = request.toHttpURLConnection(method);
conn.setRequestProperty("Connection", "close");
if (request.getCharset() != null) {
conn.setRequestProperty("Accept-Charset", request.getCharset());
}
boolean needToThrowException = false;
String exceptionMessage = "";
InputStream inputStream = null;
try {
try {
if (request.getBytes() != null) {
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", request.getContentType());
OutputStream outputStream = conn.getOutputStream();
try {
outputStream.write(request.getBytes());
} finally {
IOUtil.closeSafely(outputStream);
}
} else if (request.getFormParams() != null && request.getFormParams().size() > 0) {
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
byte[] body = request.getRequestBody().asApplicationXWwwFormUrlencoded();
OutputStream outputStream = conn.getOutputStream();
try {
outputStream.write(body);
} finally {
IOUtil.closeSafely(outputStream);
}
} else if (request.getMultipartFormData() != null && request.getMultipartFormData().size() > 0) {
conn.setDoOutput(true);
String boundary = "----CurlyHTTPClientBoundary_" + System.currentTimeMillis();
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
byte[] body = request.getRequestBody().asMultipart(boundary);
OutputStream outputStream = conn.getOutputStream();
try {
outputStream.write(body);
} finally {
IOUtil.closeSafely(outputStream);
}
}
conn.connect();
inputStream = conn.getInputStream();
} catch (IOException ioe) {
String message = method + " " + request.getUrl() + " failed because " + ioe.getMessage();
logger.warn(message);
if (logger.isDebugEnabled()) {
logger.debug(message, ioe);
}
if (request.isEnableThrowingIOException()) {
needToThrowException = true;
exceptionMessage = ioe.getMessage();
}
inputStream = conn.getErrorStream();
}
Response response = new Response();
response.setCharset(getCharsetFromContentType(conn));
response.setStatus(conn.getResponseCode());
response.setHeaderFields(conn.getHeaderFields());
Map<String, String> headers = new HashMap<String, String>();
for (String headerName : conn.getHeaderFields().keySet()) {
headers.put(headerName, conn.getHeaderField(headerName));
}
response.setHeaders(headers);
List<String> cookieFields = conn.getHeaderFields().get("Set-Cookie");
if (cookieFields != null) {
for (String cookieField : cookieFields) {
String[] tmpArray = cookieField.split("=");
if (tmpArray.length > 0) {
String name = tmpArray[0];
response.getRawCookies().put(name, cookieField);
}
}
}
if (inputStream != null) {
ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
try {
int c;
while ((c = inputStream.read()) != -1) {
bytesOutput.write(c);
}
response.setBody(bytesOutput.toByteArray());
} finally {
IOUtil.closeSafely(bytesOutput);
}
}
if (logger.isDebugEnabled()) {
StringBuilder message = new StringBuilder()
.append("\n")
.append("- HTTP Request finished. -\n")
.append(" " + method + " " + request.getUrl() + "\n")
.append(" Status: " + response.getStatus() + "\n")
.append(" Charset: " + response.getCharset() + "\n");
for (Map.Entry<String, List<String>> e : response.getHeaderFields().entrySet()) {
if (e.getValue() != null && e.getValue().size() > 0) {
message.append(" " + e.getKey() + ": " + e.getValue().get(0) + "\n");
}
}
message.append("---------\n");
logger.debug(message.toString());
}
if (needToThrowException) {
throw new HTTPIOException(exceptionMessage, response);
} else {
return response;
}
} finally {
IOUtil.closeSafely(inputStream);
conn.disconnect();
}
}
public static String urlEncode(String rawValue) {
return urlEncode(rawValue, "UTF-8");
}
public static String urlEncode(String rawValue, String charset) {
try {
return URLEncoder.encode(rawValue, charset);
} catch (UnsupportedEncodingException unexpected) {
throw new IllegalStateException(unexpected.getMessage(), unexpected);
}
}
private static Future<Response> executeAndReturn(FutureTask<Response> futureTask) {
forkJoinPool.execute(futureTask);
return futureTask;
}
private static String getCharsetFromContentType(HttpURLConnection conn) {
Pattern pattern = Pattern.compile("charset=(.+)");
String contentType = conn.getHeaderField("Content-Type");
String detectedCharset = null;
if (contentType != null) {
Matcher matcher = pattern.matcher(contentType);
if (matcher.find()) {
detectedCharset = matcher.group(1);
}
}
return detectedCharset;
}
}