001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *     ataillefer
019 */
020package org.nuxeo.ecm.automation.client.jaxrs.impl;
021
022import java.io.IOException;
023import java.io.InputStream;
024import java.nio.charset.StandardCharsets;
025import java.nio.charset.UnsupportedCharsetException;
026import java.util.Map;
027
028import javax.mail.internet.MimeMultipart;
029
030import org.apache.http.Header;
031import org.apache.http.HttpEntity;
032import org.apache.http.HttpResponse;
033import org.apache.http.client.HttpClient;
034import org.apache.http.client.methods.HttpGet;
035import org.apache.http.client.methods.HttpPost;
036import org.apache.http.client.methods.HttpRequestBase;
037import org.apache.http.client.methods.HttpUriRequest;
038import org.apache.http.client.protocol.HttpClientContext;
039import org.apache.http.entity.StringEntity;
040import org.apache.http.impl.client.BasicCookieStore;
041import org.apache.http.params.HttpParams;
042import org.apache.http.protocol.BasicHttpContext;
043import org.apache.http.protocol.HttpContext;
044import org.apache.http.util.EntityUtils;
045import org.nuxeo.ecm.automation.client.RemoteException;
046import org.nuxeo.ecm.automation.client.jaxrs.spi.Connector;
047import org.nuxeo.ecm.automation.client.jaxrs.spi.Request;
048
049/**
050 * Connector wrapping a {@link HttpClient} instance.
051 *
052 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
053 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
054 */
055public class HttpConnector implements Connector {
056
057    protected final HttpClient http;
058
059    /**
060     * Timeout in milliseconds for the socket, connection manager and connection used by {@link #http}.
061     */
062    protected final int httpConnectionTimeout;
063
064    protected final HttpContext ctx;
065
066    protected String basicAuth;
067
068    public HttpConnector(HttpClient http) {
069        this(http, 0);
070    }
071
072    /**
073     * Allows to set a timeout for the HTTP connection to avoid infinite or quite long waiting periods if:
074     * <ul>
075     * <li>Nuxeo is broken or running into an infinite loop</li>
076     * <li>the network doesn't respond at all</li>
077     * </ul>
078     *
079     * @since 5.7
080     */
081    public HttpConnector(HttpClient http, int httpConnectionTimeout) {
082        this(http, new BasicHttpContext(), httpConnectionTimeout);
083    }
084
085    public HttpConnector(HttpClient http, HttpContext ctx) {
086        this(http, ctx, 0);
087    }
088
089    /**
090     * @see {@link HttpConnector(HttpClient, long)}
091     * @since 5.7
092     */
093    public HttpConnector(HttpClient http, HttpContext ctx, int httpConnectionTimeout) {
094        ctx.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore());
095        this.http = http;
096        this.httpConnectionTimeout = httpConnectionTimeout;
097        this.ctx = ctx;
098    }
099
100    @Override
101    public Object execute(Request request) {
102        HttpRequestBase httpRequest;
103        if (request.getMethod() == Request.POST) {
104            HttpPost post = new HttpPost(request.getUrl());
105            Object obj = request.getEntity();
106            if (obj != null) {
107                HttpEntity entity;
108                if (request.isMultiPart()) {
109                    entity = new MultipartRequestEntity((MimeMultipart) obj);
110                } else {
111                    try {
112                        entity = new StringEntity(obj.toString(), StandardCharsets.UTF_8);
113                    } catch (UnsupportedCharsetException e) {
114                        // cannot happen
115                        throw new RuntimeException("Cannot encode into UTF-8", e);
116                    }
117                }
118                post.setEntity(entity);
119            }
120            httpRequest = post;
121        } else {
122            httpRequest = new HttpGet(request.getUrl());
123        }
124        try {
125            return execute(request, httpRequest);
126        } catch (IOException e) {
127            throw new RuntimeException("Cannot execute " + request, e);
128        }
129    }
130
131    protected Object execute(Request request, HttpUriRequest httpReq) throws RemoteException, IOException {
132        for (Map.Entry<String, String> entry : request.entrySet()) {
133            httpReq.setHeader(entry.getKey(), entry.getValue());
134        }
135        HttpResponse resp = executeRequestWithTimeout(httpReq);
136        HttpEntity entity = resp.getEntity();
137        try {
138            int status = resp.getStatusLine().getStatusCode();
139            // TODO kevin: check if it's enough regarding to entity content type
140            String ctype = getHeaderValue(resp, "Content-Type");
141            String disp = getHeaderValue(resp, "Content-Disposition");
142            InputStream content = entity == null ? null : entity.getContent();
143            return request.handleResult(status, ctype, disp, content);
144        } finally {
145            // needed to properly release resources and return the connection to the pool
146            EntityUtils.consume(entity);
147        }
148    }
149
150    protected HttpResponse executeRequestWithTimeout(HttpUriRequest httpReq) throws IOException {
151        // Set timeout for the socket, connection manager
152        // and connection itself
153        if (httpConnectionTimeout > 0) {
154            HttpParams httpParams = http.getParams();
155            httpParams.setIntParameter("http.socket.timeout", httpConnectionTimeout);
156            httpParams.setIntParameter("http.connection-manager.timeout", httpConnectionTimeout);
157            httpParams.setIntParameter("http.connection.timeout", httpConnectionTimeout);
158        }
159        return http.execute(httpReq, ctx);
160    }
161
162    private String getHeaderValue(HttpResponse response, String key) {
163        Header header = response.getFirstHeader(key);
164        if (header == null) {
165            return null;
166        }
167        return header.getValue();
168    }
169
170}