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 static java.nio.charset.StandardCharsets.UTF_8;
023
024import java.io.IOException;
025import java.io.InputStream;
026import java.nio.charset.UnsupportedCharsetException;
027import java.util.Map;
028
029import javax.mail.internet.MimeMultipart;
030
031import org.apache.http.Header;
032import org.apache.http.HttpEntity;
033import org.apache.http.HttpResponse;
034import org.apache.http.client.HttpClient;
035import org.apache.http.client.methods.HttpGet;
036import org.apache.http.client.methods.HttpPost;
037import org.apache.http.client.methods.HttpRequestBase;
038import org.apache.http.client.methods.HttpUriRequest;
039import org.apache.http.client.protocol.HttpClientContext;
040import org.apache.http.entity.StringEntity;
041import org.apache.http.impl.client.BasicCookieStore;
042import org.apache.http.params.HttpParams;
043import org.apache.http.protocol.BasicHttpContext;
044import org.apache.http.protocol.HttpContext;
045import org.apache.http.util.EntityUtils;
046import org.nuxeo.ecm.automation.client.RemoteException;
047import org.nuxeo.ecm.automation.client.jaxrs.spi.Connector;
048import org.nuxeo.ecm.automation.client.jaxrs.spi.Request;
049
050/**
051 * Connector wrapping a {@link HttpClient} instance.
052 *
053 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
054 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
055 */
056public class HttpConnector implements Connector {
057
058    protected final HttpClient http;
059
060    /**
061     * Timeout in milliseconds for the socket, connection manager and connection used by {@link #http}.
062     */
063    protected final int httpConnectionTimeout;
064
065    protected final HttpContext ctx;
066
067    protected String basicAuth;
068
069    public HttpConnector(HttpClient http) {
070        this(http, 0);
071    }
072
073    /**
074     * Allows to set a timeout for the HTTP connection to avoid infinite or quite long waiting periods if:
075     * <ul>
076     * <li>Nuxeo is broken or running into an infinite loop</li>
077     * <li>the network doesn't respond at all</li>
078     * </ul>
079     *
080     * @since 5.7
081     */
082    public HttpConnector(HttpClient http, int httpConnectionTimeout) {
083        this(http, new BasicHttpContext(), httpConnectionTimeout);
084    }
085
086    public HttpConnector(HttpClient http, HttpContext ctx) {
087        this(http, ctx, 0);
088    }
089
090    /**
091     * @see {@link HttpConnector(HttpClient, long)}
092     * @since 5.7
093     */
094    public HttpConnector(HttpClient http, HttpContext ctx, int httpConnectionTimeout) {
095        ctx.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore());
096        this.http = http;
097        this.httpConnectionTimeout = httpConnectionTimeout;
098        this.ctx = ctx;
099    }
100
101    @Override
102    public Object execute(Request request) {
103        HttpRequestBase httpRequest;
104        if (request.getMethod() == Request.POST) {
105            HttpPost post = new HttpPost(request.getUrl());
106            Object obj = request.getEntity();
107            if (obj != null) {
108                HttpEntity entity;
109                if (request.isMultiPart()) {
110                    entity = new MultipartRequestEntity((MimeMultipart) obj);
111                } else {
112                    try {
113                        entity = new StringEntity(obj.toString(), UTF_8);
114                    } catch (UnsupportedCharsetException e) {
115                        // cannot happen
116                        throw new RuntimeException("Cannot encode into UTF-8", e);
117                    }
118                }
119                post.setEntity(entity);
120            }
121            httpRequest = post;
122        } else {
123            httpRequest = new HttpGet(request.getUrl());
124        }
125        try {
126            return execute(request, httpRequest);
127        } catch (IOException e) {
128            throw new RuntimeException("Cannot execute " + request, e);
129        }
130    }
131
132    protected Object execute(Request request, HttpUriRequest httpReq) throws RemoteException, IOException {
133        for (Map.Entry<String, String> entry : request.entrySet()) {
134            httpReq.setHeader(entry.getKey(), entry.getValue());
135        }
136        // clear redirect locations before execution
137        ctx.removeAttribute(HttpClientContext.REDIRECT_LOCATIONS);
138        HttpResponse resp = executeRequestWithTimeout(httpReq);
139        HttpEntity entity = resp.getEntity();
140        try {
141            int status = resp.getStatusLine().getStatusCode();
142            Header[] headers = resp.getAllHeaders();
143            InputStream content = entity == null ? null : entity.getContent();
144            return request.handleResult(status, headers, content, ctx);
145        } finally {
146            // needed to properly release resources and return the connection to the pool
147            EntityUtils.consume(entity);
148        }
149    }
150
151    protected HttpResponse executeRequestWithTimeout(HttpUriRequest httpReq) throws IOException {
152        // Set timeout for the socket, connection manager
153        // and connection itself
154        if (httpConnectionTimeout > 0) {
155            HttpParams httpParams = http.getParams();
156            httpParams.setIntParameter("http.socket.timeout", httpConnectionTimeout);
157            httpParams.setIntParameter("http.connection-manager.timeout", httpConnectionTimeout);
158            httpParams.setIntParameter("http.connection.timeout", httpConnectionTimeout);
159        }
160        return http.execute(httpReq, ctx);
161    }
162
163}