org.opcfoundation.ua.transport.https.HttpsClientPendingRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.opcfoundation.ua.transport.https.HttpsClientPendingRequest.java

Source

/* Copyright (c) 1996-2015, OPC Foundation. All rights reserved.
   The source code in this file is covered under a dual-license scenario:
 - RCL: for OPC Foundation members in good-standing
 - GPL V2: everybody else
   RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
   GNU General Public License as published by the Free Software Foundation;
   version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
   This source code is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.opcfoundation.ua.transport.https;

import java.io.IOException;
import java.net.InetSocketAddress;

import javax.net.ssl.SSLPeerUnverifiedException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.nio.entity.NByteArrayEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.opcfoundation.ua.builtintypes.ServiceRequest;
import org.opcfoundation.ua.builtintypes.ServiceResponse;
import org.opcfoundation.ua.builtintypes.StatusCode;
import org.opcfoundation.ua.builtintypes.UnsignedInteger;
import org.opcfoundation.ua.common.ServiceResultException;
import org.opcfoundation.ua.core.StatusCodes;
import org.opcfoundation.ua.encoding.DecodingException;
import org.opcfoundation.ua.encoding.EncoderMode;
import org.opcfoundation.ua.encoding.EncodingException;
import org.opcfoundation.ua.encoding.IEncodeable;
import org.opcfoundation.ua.encoding.binary.BinaryDecoder;
import org.opcfoundation.ua.encoding.binary.BinaryEncoder;
import org.opcfoundation.ua.encoding.binary.EncoderCalc;
import org.opcfoundation.ua.transport.UriUtil;
import org.opcfoundation.ua.transport.impl.AsyncResultImpl;
import org.opcfoundation.ua.transport.tcp.impl.ErrorMessage;

class HttpsClientPendingRequest implements Runnable {

    private static Logger logger = LoggerFactory.getLogger(HttpsClientPendingRequest.class);

    // Client
    HttpsClient httpsClient;

    // System time in milliseconds
    long startTime = System.currentTimeMillis();

    // The time (current time) when the request timeouts
    long timeoutTime = 0;

    // Request identification
    int requestId;

    // Sync result objects
    AsyncResultImpl<ServiceResponse> result;

    // Request Message
    ServiceRequest requestMessage;

    // Post
    HttpPost httpPost;

    // Operation aborted
    UnsignedInteger abortCode = null;

    int secureChannelId;

    String securityPolicy;

    public HttpsClientPendingRequest(HttpsClient httpsClient, ServiceRequest requestMessage) {
        this.httpsClient = httpsClient;
        this.requestMessage = requestMessage;
        this.result = new AsyncResultImpl<ServiceResponse>();

        // Read time-out time
        UnsignedInteger timeoutHint = requestMessage.getRequestHeader() != null
                ? requestMessage.getRequestHeader().getTimeoutHint()
                : null;
        long clientTimeout = timeoutHint != null ? timeoutHint.longValue() : httpsClient.getOperationTimeout();
        if (clientTimeout != 0) {
            // The time when the result should be completed
            timeoutTime = startTime + clientTimeout;
        }

    }

    @Override
    public void run() {
        try {
            // Abort exit branch
            if (abortCode != null) {
                result.setError(new ServiceResultException(abortCode));
                return;
            }

            // Http Post
            InetSocketAddress inetAddress = UriUtil.getSocketAddress(httpsClient.connectUrl);
            String host = inetAddress.getHostName();
            int port = inetAddress.getPort();
            String scheme = UriUtil.getTransportProtocol(httpsClient.connectUrl);
            HttpHost httpHost = new HttpHost(host, port, scheme);
            String url = httpsClient.transportChannelSettings.getDescription().getEndpointUrl();
            String endpointId = url == null ? "" : url; //UriUtil.getEndpointName(url);
            httpPost = new HttpPost(endpointId);
            httpPost.addHeader("OPCUA-SecurityPolicy", httpsClient.securityPolicyUri);
            httpPost.addHeader("Content-Type", "application/octet-stream");

            // Calculate message length
            EncoderCalc calc = new EncoderCalc();
            calc.setEncoderContext(httpsClient.encoderCtx);
            calc.putMessage(requestMessage);
            int len = calc.getLength();

            // Assert max size is not exceeded
            int maxLen = httpsClient.encoderCtx.getMaxMessageSize();
            if (maxLen != 0 && len > maxLen) {
                final EncodingException encodingException = new EncodingException(
                        StatusCodes.Bad_EncodingLimitsExceeded, "MaxStringLength " + maxLen + " < " + len);
                logger.warn("run: failed", encodingException);
                throw encodingException;
            }

            // Encode message
            byte[] data = new byte[len];
            BinaryEncoder enc = new BinaryEncoder(data);
            enc.setEncoderContext(httpsClient.encoderCtx);
            enc.setEncoderMode(EncoderMode.NonStrict);
            enc.putMessage(requestMessage);
            httpPost.setEntity(new NByteArrayEntity(data));

            // Abort exit branch
            if (abortCode != null) {
                result.setError(new ServiceResultException(abortCode));
                return;
            }

            // Execute Post

            HttpResponse httpResponse;
            try {
                httpResponse = httpsClient.httpclient.execute(httpHost, httpPost);
            } catch (SSLPeerUnverifiedException e) {
                // Currently, TLS_1_2 is not supported by JSSE implementations, for some odd reason
                // and it will give this exception when used.
                // Also, if the server certificate is rejected, we will get this error
                result.setError(new ServiceResultException(StatusCodes.Bad_SecurityPolicyRejected, e,
                        "Could not negotiate a TLS security cipher or the server did not provide a valid certificate."));
                return;
            }
            HttpEntity entity = httpResponse.getEntity();

            // Error response
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                UnsignedInteger uacode = StatusCodes.Bad_UnknownResponse;
                if (statusCode == 501)
                    uacode = StatusCodes.Bad_ServiceUnsupported;
                String msg = EntityUtils.toString(entity);
                result.setError(new ServiceResultException(uacode, statusCode + ": " + msg));
                return;
            }

            // Abort exit branch
            if (abortCode != null) {
                result.setError(new ServiceResultException(abortCode));
                return;
            }

            // Decode Message
            data = EntityUtils.toByteArray(entity);

            BinaryDecoder dec = new BinaryDecoder(data);
            dec.setEncoderContext(httpsClient.encoderCtx);
            IEncodeable response = dec.getMessage();

            // Client sent an error
            if (response instanceof ErrorMessage) {
                ErrorMessage error = (ErrorMessage) response;
                ServiceResultException errorResult = new ServiceResultException(new StatusCode(error.getError()),
                        error.getReason());
                result.setError(errorResult);
                return;
            }

            try {
                // Client sent a valid message
                result.setResult((ServiceResponse) response);
            } catch (ClassCastException e) {
                result.setError(new ServiceResultException(e));
                logger.error("Cannot cast response to ServiceResponse, response=" + response.getClass(), e);
            }
        } catch (EncodingException e) {
            // Internal Error
            result.setError(new ServiceResultException(StatusCodes.Bad_EncodingError, e));
        } catch (ClientProtocolException e) {
            result.setError(new ServiceResultException(StatusCodes.Bad_CommunicationError, e));
        } catch (IOException e) {
            if (abortCode != null) {
                result.setError(new ServiceResultException(abortCode, e));
            } else {
                result.setError(new ServiceResultException(StatusCodes.Bad_CommunicationError, e));
            }
        } catch (DecodingException e) {
            result.setError(new ServiceResultException(StatusCodes.Bad_DecodingError, e));
        } catch (ServiceResultException e) {
            result.setError(e);
        } catch (RuntimeException rte) {
            // http-client seems to be throwing these, IllegalArgumentException for one
            result.setError(new ServiceResultException(rte));
        } finally {
            httpsClient.requests.remove(requestId);
        }
    }

    public void cancel() {
        if (httpsClient.requests.remove(this) == null)
            return;
        abortCode = StatusCodes.Bad_RequestCancelledByRequest;
        HttpPost post = httpPost;
        if (post != null)
            post.abort();
    }

    public void timeout() {
        if (httpsClient.requests.remove(requestId) == null)
            return;
        abortCode = StatusCodes.Bad_Timeout;
        HttpPost post = httpPost;
        if (post != null)
            post.abort();
        result.setError(new ServiceResultException(abortCode));
    }

}