001/* 002 * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 * ataillefer 012 */ 013package org.nuxeo.ecm.automation.client.jaxrs.impl; 014 015import java.io.IOException; 016import java.nio.charset.UnsupportedCharsetException; 017import java.util.Map; 018 019import javax.mail.internet.MimeMultipart; 020import javax.ws.rs.core.Response; 021 022import java.nio.charset.StandardCharsets; 023import org.apache.http.Header; 024import org.apache.http.HttpEntity; 025import org.apache.http.HttpResponse; 026import org.apache.http.client.HttpClient; 027import org.apache.http.client.methods.HttpGet; 028import org.apache.http.client.methods.HttpPost; 029import org.apache.http.client.methods.HttpRequestBase; 030import org.apache.http.client.methods.HttpUriRequest; 031import org.apache.http.client.protocol.HttpClientContext; 032import org.apache.http.entity.StringEntity; 033import org.apache.http.impl.client.BasicCookieStore; 034import org.apache.http.params.HttpParams; 035import org.apache.http.protocol.BasicHttpContext; 036import org.apache.http.protocol.HttpContext; 037import org.apache.http.util.EntityUtils; 038 039import org.nuxeo.ecm.automation.client.RemoteException; 040import org.nuxeo.ecm.automation.client.jaxrs.spi.Connector; 041import org.nuxeo.ecm.automation.client.jaxrs.spi.Request; 042 043/** 044 * Connector wrapping a {@link HttpClient} instance. 045 * 046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 047 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a> 048 */ 049public class HttpConnector implements Connector { 050 051 protected final HttpClient http; 052 053 /** 054 * Timeout in milliseconds for the socket, connection manager and connection used by {@link #http}. 055 */ 056 protected final int httpConnectionTimeout; 057 058 protected final HttpContext ctx; 059 060 protected String basicAuth; 061 062 public HttpConnector(HttpClient http) { 063 this(http, 0); 064 } 065 066 /** 067 * Allows to set a timeout for the HTTP connection to avoid infinite or quite long waiting periods if: 068 * <ul> 069 * <li>Nuxeo is broken or running into an infinite loop</li> 070 * <li>the network doesn't respond at all</li> 071 * </ul> 072 * 073 * @since 5.7 074 */ 075 public HttpConnector(HttpClient http, int httpConnectionTimeout) { 076 this(http, new BasicHttpContext(), httpConnectionTimeout); 077 } 078 079 public HttpConnector(HttpClient http, HttpContext ctx) { 080 this(http, ctx, 0); 081 } 082 083 /** 084 * @see {@link HttpConnector(HttpClient, long)} 085 * @since 5.7 086 */ 087 public HttpConnector(HttpClient http, HttpContext ctx, int httpConnectionTimeout) { 088 ctx.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore()); 089 this.http = http; 090 this.httpConnectionTimeout = httpConnectionTimeout; 091 this.ctx = ctx; 092 } 093 094 @Override 095 public Object execute(Request request) { 096 HttpRequestBase httpRequest = null; 097 if (request.getMethod() == Request.POST) { 098 HttpPost post = new HttpPost(request.getUrl()); 099 Object obj = request.getEntity(); 100 if (obj != null) { 101 HttpEntity entity = null; 102 if (request.isMultiPart()) { 103 entity = new MultipartRequestEntity((MimeMultipart) obj); 104 } else { 105 try { 106 entity = new StringEntity(obj.toString(), StandardCharsets.UTF_8); 107 } catch (UnsupportedCharsetException e) { 108 // cannot happen 109 throw new RuntimeException("Cannot encode into UTF-8", e); 110 } 111 } 112 post.setEntity(entity); 113 } 114 httpRequest = post; 115 } else { 116 httpRequest = new HttpGet(request.getUrl()); 117 } 118 try { 119 return execute(request, httpRequest); 120 } catch (IOException e) { 121 throw new RuntimeException("Cannot execute " + request, e); 122 } 123 } 124 125 protected Object execute(Request request, HttpUriRequest httpReq) throws RemoteException, IOException { 126 for (Map.Entry<String, String> entry : request.entrySet()) { 127 httpReq.setHeader(entry.getKey(), entry.getValue()); 128 } 129 HttpResponse resp = executeRequestWithTimeout(httpReq); 130 HttpEntity entity = resp.getEntity(); 131 try { 132 int status = resp.getStatusLine().getStatusCode(); 133 if (entity == null) { 134 if (status < Response.Status.BAD_REQUEST.getStatusCode()) { 135 return null; 136 } 137 throw new RemoteException(status, "ServerError", "Server Error", ""); 138 } 139 Header ctypeHeader = entity.getContentType(); 140 if (ctypeHeader == null) { // handle broken responses with no ctype 141 if (status != Response.Status.OK.getStatusCode()) { 142 // this may happen when login failed 143 throw new RemoteException(status, "ServerError", "Server Error", ""); 144 } 145 return null; // cannot handle responses with no ctype 146 } 147 String ctype = ctypeHeader.getValue(); 148 String disp = null; 149 Header[] hdisp = resp.getHeaders("Content-Disposition"); 150 if (hdisp != null && hdisp.length > 0) { 151 disp = hdisp[0].getValue(); 152 } 153 return request.handleResult(status, ctype, disp, entity.getContent()); 154 } finally { 155 // needed to properly release resources and return the connection to the pool 156 EntityUtils.consume(entity); 157 } 158 } 159 160 protected HttpResponse executeRequestWithTimeout(HttpUriRequest httpReq) throws IOException { 161 // Set timeout for the socket, connection manager 162 // and connection itself 163 if (httpConnectionTimeout > 0) { 164 HttpParams httpParams = http.getParams(); 165 httpParams.setIntParameter("http.socket.timeout", httpConnectionTimeout); 166 httpParams.setIntParameter("http.connection-manager.timeout", httpConnectionTimeout); 167 httpParams.setIntParameter("http.connection.timeout", httpConnectionTimeout); 168 } 169 return http.execute(httpReq, ctx); 170 } 171 172}