All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.documentdb.GatewayProxy Maven / Gradle / Ivy

/*
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 */

package com.microsoft.azure.documentdb;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;


class GatewayProxy {

    private URI serviceEndpoint;
    private Map defaultHeaders;
    private String masterKey;
    private Map resourceTokens;
    private ConnectionPolicy connectionPolicy;
    private HttpClient httpClient;
    private HttpClient mediaHttpClient;
    private PoolingClientConnectionManager connectionManager;
    private DocumentClient.QueryCompatibilityMode queryCompatibilityMode;

    public GatewayProxy(URI serviceEndpoint,
                        ConnectionPolicy connectionPolicy,
                        ConsistencyLevel consistencyLevel,
                        DocumentClient.QueryCompatibilityMode queryCompatibilityMode,
                        String masterKey,
                        Map resourceTokens,
                        UserAgentContainer userAgentContainer) {
        this.serviceEndpoint = serviceEndpoint;
        this.defaultHeaders = new HashMap();
        this.defaultHeaders.put(HttpConstants.HttpHeaders.CACHE_CONTROL,
                                "no-cache");
        this.defaultHeaders.put(HttpConstants.HttpHeaders.VERSION,
                                HttpConstants.Versions.CURRENT_VERSION);

        if (userAgentContainer == null) {
            userAgentContainer = new UserAgentContainer();
        }

        this.defaultHeaders.put(HttpConstants.HttpHeaders.USER_AGENT, userAgentContainer.getUserAgent());

        if (consistencyLevel != null) {
            this.defaultHeaders.put(HttpConstants.HttpHeaders.CONSISTENCY_LEVEL,
                                    consistencyLevel.toString());
        }

        this.connectionPolicy = connectionPolicy;
        this.queryCompatibilityMode = queryCompatibilityMode;
        this.masterKey = masterKey;
        this.resourceTokens = resourceTokens;

        // Initialize connection manager.
        this.connectionManager = new PoolingClientConnectionManager(SchemeRegistryFactory.createDefault());
        this.connectionManager.setMaxTotal(this.connectionPolicy.getMaxPoolSize());
        this.connectionManager.setDefaultMaxPerRoute(this.connectionPolicy.getMaxPoolSize());
        this.connectionManager.closeIdleConnections(this.connectionPolicy.getIdleConnectionTimeout(), TimeUnit.SECONDS);
    }

    public DocumentServiceResponse doCreate(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performPostRequest(request);
    }
    
    public DocumentServiceResponse doUpsert(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performPostRequest(request);
    }

    public DocumentServiceResponse doRead(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performGetRequest(request);
    }

    public DocumentServiceResponse doReplace(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performPutRequest(request);
    }

    public DocumentServiceResponse doDelete(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performDeleteRequest(request);
    }

    public DocumentServiceResponse doExecute(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performPostRequest(request);
    }

    public DocumentServiceResponse doReadFeed(DocumentServiceRequest request)
        throws DocumentClientException {
        return this.performGetRequest(request);
    }

    public DocumentServiceResponse doSQLQuery(DocumentServiceRequest request)
        throws DocumentClientException {
        request.getHeaders().put(HttpConstants.HttpHeaders.IS_QUERY, "true");

        switch (this.queryCompatibilityMode) {
            case SqlQuery:
                request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE,
                                         RuntimeConstants.MediaTypes.SQL);
                break;
            case Default:
            case Query:
            default:
                request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE,
                                         RuntimeConstants.MediaTypes.QUERY_JSON);
                break;
        }

        return this.performPostRequest(request);
    }

    private HttpClient getHttpClient(boolean isForMedia) {
        if (isForMedia) {
            if (this.mediaHttpClient == null) this.mediaHttpClient = this.createHttpClient(isForMedia);
            return this.mediaHttpClient;
        } else {
            if (this.httpClient == null) this.httpClient = this.createHttpClient(isForMedia);
            return this.httpClient;
        }
    }

    /**
     * Only one instance is created for the httpClient for optimization.
     * A PoolingClientConnectionManager is used with the Http client
     * to be able to reuse connections and execute requests concurrently.
     * A timeout for closing each connection is set so that connections don't leak.
     * A timeout is set for requests to avoid deadlocks.
     * @return the created HttpClient
     */
    private HttpClient createHttpClient(boolean isForMedia) {
        ProxySelectorRoutePlanner ps = new ProxySelectorRoutePlanner(
                SchemeRegistryFactory.createDefault(), ProxySelector.getDefault());

        DefaultHttpClient defaultHttpClient = new DefaultHttpClient(this.connectionManager);
        defaultHttpClient.setRoutePlanner(ps);
        
        HttpClient httpClient = defaultHttpClient;
        HttpParams httpParams = httpClient.getParams();
        
        // Setting Cookie policy on httpclient to ignoring server cookies as we don't use them
        httpParams.setParameter("http.protocol.single-cookie-header", true);
        httpParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);

        if (isForMedia) {
            HttpConnectionParams.setConnectionTimeout(httpParams,
                                                      this.connectionPolicy.getMediaRequestTimeout() * 1000);
            HttpConnectionParams.setSoTimeout(httpParams, this.connectionPolicy.getMediaRequestTimeout() * 1000);
        } else {
            HttpConnectionParams.setConnectionTimeout(httpParams, this.connectionPolicy.getRequestTimeout() * 1000);
            HttpConnectionParams.setSoTimeout(httpParams, this.connectionPolicy.getRequestTimeout() * 1000);
        }

        return httpClient;
    }

    private void putMoreContentIntoDocumentServiceRequest(
        DocumentServiceRequest request,
        String httpMethod) {
        if (this.masterKey != null) {
            final Date currentTime = new Date();
            final SimpleDateFormat sdf =
                new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
            String xDate = sdf.format(currentTime);

            request.getHeaders().put(HttpConstants.HttpHeaders.X_DATE, xDate);
        }

        if (this.masterKey != null || this.resourceTokens != null) {
            String path = request.getPath();
            path = Utils.trimBeginingAndEndingSlashes(path);
            
            String resourceName = "";
            
            if(Utils.isNameBased(path)) {
                String[] segments = path.split("/");
                
                if (segments.length % 2 == 0) {
                    // if path has even segments, it is the individual resource like dbs/db1/colls/coll1
                    if (Utils.IsResourceType(segments[segments.length-2])) {
                        resourceName = path;
                    }
                }
                else {
                    // if path has odd segments, get the parent(dbs/db1 from dbs/db1/colls)
                    if (Utils.IsResourceType(segments[segments.length - 1])) {
                        resourceName = path.substring(0, path.lastIndexOf("/"));
                    }
                }
            }
            else {
                resourceName = request.getResourceId().toLowerCase();
            }
            
            String authorization =
                this.getAuthorizationToken(resourceName,
                                           request.getPath(),
                                           request.getResourceType(),
                                           httpMethod,
                                           request.getHeaders(),
                                           this.masterKey,
                                           this.resourceTokens);
            try {
                authorization = URLEncoder.encode(authorization, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException("Failed to encode authtoken.", e);
            }
            request.getHeaders().put(HttpConstants.HttpHeaders.AUTHORIZATION,
                                     authorization);
        }

        if ((httpMethod == HttpConstants.HttpMethods.POST ||
             httpMethod ==  HttpConstants.HttpMethods.PUT) &&
            !request.getHeaders().containsKey(
                HttpConstants.HttpHeaders.CONTENT_TYPE)) {
            request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE,
                                     RuntimeConstants.MediaTypes.JSON);
        }

        if (!request.getHeaders().containsKey(
                HttpConstants.HttpHeaders.ACCEPT)) {
            request.getHeaders().put(HttpConstants.HttpHeaders.ACCEPT,
                                     RuntimeConstants.MediaTypes.JSON);
        }
    }

    private void fillHttpRequestBaseWithHeaders(Map headers, HttpRequestBase httpBase) {
        // Add default headers.
        for (Map.Entry entry : this.defaultHeaders.entrySet()) {
            httpBase.setHeader(entry.getKey(), entry.getValue());
        }
        // Add override headers.
        if (headers != null) {
            for (Map.Entry entry : headers.entrySet()) {
                httpBase.setHeader(entry.getKey(), entry.getValue());
            }
        }
    }

    private void maybeThrowException(HttpResponse response) throws DocumentClientException {
        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode >= HttpConstants.StatusCodes.MINIMUM_STATUSCODE_AS_ERROR_GATEWAY) {
            HttpEntity httpEntity = response.getEntity();
            String body = "";
            if (httpEntity != null) {
                try {
                    body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                    EntityUtils.consume(response.getEntity());
                } catch (ParseException | IOException e) {
                    e.printStackTrace();
                    // body is empty.
                    throw new IllegalStateException("Failed to get content from the http response", e);
                }
            }

            Map responseHeaders = new HashMap();
            for (Header header : response.getAllHeaders()) {
                responseHeaders.put(header.getName(), header.getValue());
            }

            throw new DocumentClientException(statusCode, new Error(body), responseHeaders);
        }
    }

    private DocumentServiceResponse performDeleteRequest(
            DocumentServiceRequest request) throws DocumentClientException {
        putMoreContentIntoDocumentServiceRequest(
            request,
            HttpConstants.HttpMethods.DELETE);
        URI uri;
        try {
            uri = new URI("https",
                          null,
                          this.serviceEndpoint.getHost(),
                          this.serviceEndpoint.getPort(),
                          request.getPath(),
                          null,  // Query string not used.
                          null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Incorrect uri from request.",
                                               e);
        }

        HttpDelete httpDelete = new HttpDelete(uri);
        this.fillHttpRequestBaseWithHeaders(request.getHeaders(), httpDelete);
        HttpResponse response = null;
        try {
            response = this.getHttpClient(request.getIsMedia()).execute(httpDelete);
        } catch (IOException e) {
            httpDelete.releaseConnection();
            throw new IllegalStateException("Http client execution failed.", e);
        }

        this.maybeThrowException(response);

        // No content in delete request, we can release the connection directly;
        httpDelete.releaseConnection();
        return new DocumentServiceResponse(response);
    }

    private DocumentServiceResponse performGetRequest(DocumentServiceRequest request) throws DocumentClientException {
        putMoreContentIntoDocumentServiceRequest(request,
                                                 HttpConstants.HttpMethods.GET);
        URI uri;
        try {
            uri = new URI("https",
                          null,
                          this.serviceEndpoint.getHost(),
                          this.serviceEndpoint.getPort(),
                          request.getPath(),
                          null,  // Query string not used.
                          null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Incorrect uri from request.",
                                               e);
        }

        HttpGet httpGet = new HttpGet(uri);
        this.fillHttpRequestBaseWithHeaders(request.getHeaders(), httpGet);
        HttpResponse response = null;
        try {
            response = this.getHttpClient(request.getIsMedia()).execute(httpGet);
        } catch (IOException e) {
            httpGet.releaseConnection();
            throw new IllegalStateException("Http client execution failed.", e);
        }

        this.maybeThrowException(response);
        return new DocumentServiceResponse(response);
    }

    private DocumentServiceResponse performPostRequest(DocumentServiceRequest request) throws DocumentClientException {
        putMoreContentIntoDocumentServiceRequest(
            request,
            HttpConstants.HttpMethods.POST);
        URI uri;
        try {
            uri = new URI("https",
                          null,
                          this.serviceEndpoint.getHost(),
                          this.serviceEndpoint.getPort(),
                          request.getPath(),
                          null,  // Query string not used.
                          null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Incorrect uri from request.",
                                               e);
        }

        HttpPost httpPost = new HttpPost(uri);
        this.fillHttpRequestBaseWithHeaders(request.getHeaders(), httpPost);
        httpPost.setEntity(request.getBody());
        HttpResponse response = null;
        try {
            response = this.getHttpClient(request.getIsMedia()).execute(httpPost);
        } catch (IOException e) {
            httpPost.releaseConnection();
            throw new IllegalStateException("Http client execution failed.", e);
        }

        this.maybeThrowException(response);
        return new DocumentServiceResponse(response);
    }

    private DocumentServiceResponse performPutRequest(DocumentServiceRequest request) throws DocumentClientException {
        putMoreContentIntoDocumentServiceRequest(request,
                                                 HttpConstants.HttpMethods.PUT);
        URI uri;
        try {
            uri = new URI("https",
                          null,
                          this.serviceEndpoint.getHost(),
                          this.serviceEndpoint.getPort(),
                          request.getPath(),
                          null,  // Query string not used.
                          null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Incorrect uri from request.",
                                               e);
        }

        HttpPut httpPut = new HttpPut(uri);
        this.fillHttpRequestBaseWithHeaders(request.getHeaders(), httpPut);
        httpPut.setEntity(request.getBody());
        HttpResponse response = null;
        try {
            httpPut.releaseConnection();
            response = this.getHttpClient(request.getIsMedia()).execute(httpPut);
        } catch (IOException e) {
            throw new IllegalStateException("Http client execution failed.", e);
        }

        this.maybeThrowException(response);
        return new DocumentServiceResponse(response);
    }

    private String getAuthorizationToken(String resourceOrOwnerId,
                                         String path,
                                         ResourceType resourceType,
                                         String requestVerb,
                                         Map headers,
                                         String masterKey,
                                         Map resourceTokens) {
        if (masterKey != null) {
            return AuthorizationHelper.GenerateKeyAuthorizationSignature(
                requestVerb,
                resourceOrOwnerId,
                resourceType,
                headers,
                masterKey);
        } else if (resourceTokens != null) {
            return AuthorizationHelper.GetAuthorizationTokenUsingResourceTokens(
                resourceTokens, path, resourceOrOwnerId);
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy