package detective.task; import groovy.lang.GString; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.charset.Charset; import java.util.Map; import java.util.Scanner; import org.apache.commons.codec.binary.Base64; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ContentType; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.HTTP; import com.amazonaws.HttpMethod; import detective.common.annotation.ThreadSafe; import detective.core.Parameters; import detective.core.TaskException; import detective.core.config.ConfigException; import detective.core.services.DetectiveFactory; /** * * HTTP Client Task<br> * <p></p> * Support GET, PUT, POST, DELETE, HEAD and OPTIONS * * <h3>Input</h3> * <pre> * http.use_shared_cookies: default true, identify if this httpclient will read cookies from share data section * http.cookies: optional, output of other HTTPClientTask * http.address: a String or a Java URI * http.method: GET, PUT, POST, DELETE, HEAD, OPTIONS, optional, default to POST * * http.post.string: optional, the data as a plain text which will post to server * * http.post.file.filename: optional, the file you'd like to upload * * </pre> * <h3>Output</h3> * <pre> * http.cookies: the http context current task have (may created by this task or passed in from input) * http.output * http.status.code * http.header.* : all headers returned from server * get * * </pre> * * <ul> * <li> * GET The HTTP GET method is defined in section 9.3 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The GET method means retrieve whatever information (in the form of an * entity) is identified by the Request-URI. * </li> * <li> * PUT HTTP PUT method. * The HTTP PUT method is defined in section 9.6 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The PUT method requests that the enclosed entity be stored under the * supplied Request-URI. * </li> * <li> * POST HTTP POST method. * The HTTP POST method is defined in section 9.5 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The POST method is used to request that the origin server accept the entity * enclosed in the request as a new subordinate of the resource identified by * the Request-URI in the Request-Line. * </li> * <li> * DELETE HTTP DELETE method * The HTTP DELETE method is defined in section 9.7 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The DELETE method requests that the origin server delete the resource * identified by the Request-URI. * </li> * <li> * HEAD HTTP HEAD method. * The HTTP HEAD method is defined in section 9.4 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The HEAD method is identical to GET except that the server MUST NOT * return a message-body in the response. * </li> * <li> * OPTIONS HTTP OPTIONS method. * The HTTP OPTIONS method is defined in section 9.2 of * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: * The OPTIONS method represents a request for information about the * communication options available on the request/response chain * identified by the Request-URI. * </li> * * </ul> * * It able to share cookies which means you can share session between different requests in most cases. * * HttpClient Connection Manager Document Link: http://hc.apache.org/httpcomponents-client-4.3.x/tutorial/html/connmgmt.html * * <p></p> * * @author James Luo * */ @ThreadSafe public class HttpClientTask extends AbstractTask{ public static final String PARAM_HTTP_COOKIES = "http.cookies"; public enum HttpMethod { GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH; } @Override protected void doExecute(Parameters config, Parameters output) { CloseableHttpClient httpClient = DetectiveFactory.INSTANCE.getHttpClient(); CookieStore cookieStore = null; boolean useSharedCookies = this.readAsString(config, "http.use_shared_cookies", "true", true, null).equals("true"); if (useSharedCookies){ cookieStore = this.readOptional(config, PARAM_HTTP_COOKIES, null, CookieStore.class); } if (cookieStore == null){ cookieStore = new BasicCookieStore(); } HttpClientContext context = HttpClientContext.create(); context.setCookieStore(cookieStore); context.setRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY).build()); String method = this.readAsString(config, "http.method", "post", true, null); HttpMethod m = HttpMethod.valueOf(method.toUpperCase()); Object address = this.readAsObject(config, "http.address", null, false, "please provider address your request send to", Object.class); URI uri = null; if (address instanceof String) uri = URI.create((String)address); else if (address instanceof GString) uri = URI.create(address.toString()); else if (address instanceof URI) uri = (URI)address; else throw new ConfigException("the address you provided have to be a String or java URI, however your type is " + address.getClass().getName()); HttpUriRequest request = this.createHttpUriRequest(m, uri); request.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); String userName = this.readAsString(config, "http.auth.username", null, true, "Adding basic http header auth to request"); String password = this.readAsString(config, "http.auth.password", null, true, ""); if (null != userName && null != password) { request = addBasicAuthentication(request, userName, password); } String authorizationHeader = this.readAsString(config, "http.auth.header", null, true, "No authorization header found, skipping token auth"); if (authorizationHeader != null) { request = addAuthorizationHeader(request, authorizationHeader); } if (request instanceof HttpEntityEnclosingRequestBase){ if (config.containsKey("http.post.string")){ Object postText = this.readOptional(config, "http.post.string", null, Object.class); setupPostRequest((HttpEntityEnclosingRequestBase)request, postText.toString()); }else if (config.containsKey("http.post.file.filename")){ String filename = this.readOptional(config, "http.post.file.filename", null, String.class); this.setupPostWithFileName((HttpPost)request, filename); } } try { CloseableHttpResponse response = httpClient.execute(request, context); try { output.put(PARAM_HTTP_COOKIES, cookieStore); output.put("http.protocal.name", response.getStatusLine().getProtocolVersion().getProtocol()); output.put("http.protocal.version.major", response.getStatusLine().getProtocolVersion().getMajor()); output.put("http.protocal.version.minor", response.getStatusLine().getProtocolVersion().getMinor()); output.put("http.protocal.description", response.getStatusLine().getProtocolVersion().toString()); output.put("http.status.code", response.getStatusLine().getStatusCode()); output.put("http.status.reason", response.getStatusLine().getReasonPhrase()); //Headers for (Header header : response.getAllHeaders()){ output.put("http.header." + header.getName(), header.getValue()); } //Cookies //output.put("http.cookies", context.getCookieStore().getCookies()); //Entity HttpEntity entity = response.getEntity(); // add by George Zeng, for adding a content back to do more actions Scanner scanner = new Scanner(entity.getContent()); StringBuilder content = new StringBuilder(); while(scanner.hasNext()) { content.append(scanner.nextLine()).append("\n"); } scanner.close(); output.put("http.content", content.toString()); output.put("http.content.string", content.toString()); output.put("http.content.length", entity.getContentLength()); if (entity.getContentEncoding() != null) output.put("http.content." + entity.getContentEncoding().getName(), entity.getContentEncoding().getValue()); if (entity.getContentType() != null) output.put("http.content." + entity.getContentType().getName(), entity.getContentType().getValue()); //System.out.println(entity.toString()); } finally { response.close(); } } catch (ClientProtocolException ex) { throw new TaskException(ex); } catch (IOException ex) { throw new TaskException(ex); } } protected HttpEntityEnclosingRequestBase setupPostRequest(HttpEntityEnclosingRequestBase request, String postText){ if (postText == null || postText.length() == 0) return request; StringEntity entity = new StringEntity(postText, "UTF-8"); BasicHeader basicHeader = new BasicHeader(HTTP.CONTENT_TYPE,"application/json"); //request.getParams().setBooleanParameter("http.protocol.expect-continue", false); entity.setContentType(basicHeader); request.setEntity(entity); return request; } protected HttpPost setupPostWithFileName(HttpPost request, String fileName){ if (fileName == null || fileName.length() == 0) return request; ContentBody fileBody = new FileBody(new File(fileName), ContentType.DEFAULT_BINARY); MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create(); multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setBoundary("boundaryABC--WebKitFormBoundaryGMApUciYGfDuPa49"); multipartEntity.addPart("file", fileBody); request.setEntity(multipartEntity.build()); // MultipartEntity mpEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, "boundaryABC--WebKitFormBoundaryGMApUciYGfDuPa49", Charset.forName("UTF-8")); // mpEntity.addPart("userfile", fileBody); // request.setEntity(mpEntity); // FileEntity entity = new FileEntity(new File(fileName), ContentType.APPLICATION_OCTET_STREAM); // BasicHeader basicHeader = new BasicHeader(HTTP.CONTENT_TYPE,"application/json"); //request.getParams().setBooleanParameter("http.protocol.expect-continue", false); // entity.setContentType(basicHeader); // request.setEntity(mpEntity); return request; } /** * Create a Commons HttpMethodBase object for the given HTTP method and URI specification. * @param httpMethod the HTTP method * @param uri the URI * @return the Commons HttpMethodBase object */ protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) { switch (httpMethod) { case GET: return new HttpGet(uri); case DELETE: return new HttpDeleteWithBody(uri); case HEAD: return new HttpHead(uri); case OPTIONS: return new HttpOptions(uri); case POST: return new HttpPost(uri); case PUT: return new HttpPut(uri); case TRACE: return new HttpTrace(uri); case PATCH: return new HttpPatch(uri); default: throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod); } } protected HttpUriRequest addBasicAuthentication(HttpUriRequest request, String user, String pass) { String auth = user + ":" + pass; byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("UTF-8"))); String authHeader = "Basic " + new String(encodedAuth); request.setHeader(HttpHeaders.AUTHORIZATION, authHeader); return request; } protected HttpUriRequest addAuthorizationHeader(HttpUriRequest request, String headerValue) { request.setHeader(HttpHeaders.AUTHORIZATION, headerValue); return request; } }