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

com.sun.xml.rpc.client.http.HttpClientTransport Maven / Gradle / Ivy

/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

/**
 * @author JAX-RPC Development Team
 */
package com.sun.xml.rpc.client.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import com.sun.xml.messaging.saaj.util.ByteInputStream;
import com.sun.xml.rpc.client.ClientTransport;
import com.sun.xml.rpc.client.ClientTransportException;
import com.sun.xml.rpc.client.StubPropertyConstants;
import com.sun.xml.rpc.encoding.simpletype.SimpleTypeEncoder;
import com.sun.xml.rpc.encoding.simpletype.XSDBase64BinaryEncoder;
import com.sun.xml.rpc.soap.message.SOAPMessageContext;
import com.sun.xml.rpc.util.exception.LocalizableExceptionAdapter;
import com.sun.xml.rpc.util.localization.Localizable;

/**
 *
 * @author JAX-RPC Development Team
 */
public class HttpClientTransport
    implements ClientTransport, StubPropertyConstants {

    public static final String HTTP_SOAPACTION_PROPERTY = "http.soap.action";
    private static final SimpleTypeEncoder base64Encoder =
        XSDBase64BinaryEncoder.getInstance();
    private static String LAST_ENDPOINT = "";
    private static boolean redirect = true;
    private static final int START_REDIRECT_COUNT = 3;
    private static int redirectCount = START_REDIRECT_COUNT;

    public HttpClientTransport() {
        this(null);
    }

    public HttpClientTransport(OutputStream logStream) {
        try {
            _messageFactory = MessageFactory.newInstance();
            _logStream = logStream;
        } catch (Exception e) {
            throw new ClientTransportException("http.client.cannotCreateMessageFactory");
        }
    }

    public void invoke(String endpoint, SOAPMessageContext context)
        throws ClientTransportException {

        //using an HttpURLConnection the soap message is sent
        //over the wire
        try {

            HttpURLConnection httpConnection =
                createHttpConnection(endpoint, context);

            setupContextForInvoke(context);

            CookieJar cookieJar = sendCookieAsNeeded(context, httpConnection);

            moveHeadersFromContextToConnection(context, httpConnection);
            
            if (DEBUG) {
                checkMessageContentType(httpConnection.getRequestProperty("Content-Type"), false);
            }

            writeMessageToConnection(context, httpConnection);
            
            boolean isFailure = connectForResponse(httpConnection, context);
            int statusCode = httpConnection.getResponseCode();

            //http URL redirection does not redirect http requests
            //to an https endpoint probably due to a bug in the jdk
            //or by intent - to workaround this if an error code
            //of HTTP_MOVED_TEMP or HTTP_MOVED_PERM is received then
            //the jaxrpc client will reinvoke the original request
            //to the new endpoint - kw bug 4890118
            if (checkForRedirect(statusCode)){
                redirectRequest(httpConnection, context);
                return;
            }

            MimeHeaders headers = collectResponseMimeHeaders(httpConnection);
            
            saveCookieAsNeeded(context, httpConnection, cookieJar);

            SOAPMessage response = null;
            //get the response from the HttpURLConnection
            try {
                response = readResponse(httpConnection, isFailure, headers);
            } catch (SOAPException e) {
                if (statusCode == HttpURLConnection.HTTP_NO_CONTENT
                    || (isFailure
                        && statusCode != HttpURLConnection.HTTP_INTERNAL_ERROR)) {
                    throw new ClientTransportException(
                        "http.status.code",
                        new Object[] {
                            new Integer(statusCode),
                            httpConnection.getResponseMessage()});
                }
                throw e;
            }
            httpConnection = null;

            logResponseMessage(context, response);

            if (DEBUG) {
                checkMessageContentType(headers.getHeader("Content-Type")[0], true);
            }
            
            context.setMessage(response);
            // do not set the failure flag, because stubs cannot rely on it,
            // since transports different from HTTP may not be able to set it
            // context.setFailure(isFailure);

        } catch (ClientTransportException e) {
            // let these through unmodified
            throw e;
        } catch (Exception e) {
            if (e instanceof Localizable) {
                throw new ClientTransportException(
                    "http.client.failed",
                    (Localizable) e);
            } else {
                throw new ClientTransportException(
                    "http.client.failed",
                    new LocalizableExceptionAdapter(e));
            }
        }
    }

    public void invokeOneWay(String endpoint, SOAPMessageContext context) {

        //one way send of message over the wire
        //no response will be returned
        try {
            HttpURLConnection httpConnection =
                createHttpConnection(endpoint, context);

            setupContextForInvoke(context);

            moveHeadersFromContextToConnection(context, httpConnection);

            writeMessageToConnection(context, httpConnection);

            forceMessageToBeSent(httpConnection, context);

        } catch (Exception e) {
            if (e instanceof Localizable) {
                throw new ClientTransportException(
                    "http.client.failed",
                    (Localizable) e);
            } else {
                throw new ClientTransportException(
                    "http.client.failed",
                    new LocalizableExceptionAdapter(e));
            }
        }
    }

    protected void logResponseMessage(
        SOAPMessageContext context,
        SOAPMessage response)
        throws IOException, SOAPException {

        if (_logStream != null) {
            String s = "Response\n";
            _logStream.write(s.getBytes());
            s =
                "Http Status Code: "
                    + context.getProperty(StubPropertyConstants.HTTP_STATUS_CODE)
                    + "\n\n";
            _logStream.write(s.getBytes());
            for (Iterator iter =
                response.getMimeHeaders().getAllHeaders();
                iter.hasNext();
                ) {
                MimeHeader header = (MimeHeader) iter.next();
                s = header.getName() + ": " + header.getValue() + "\n";
                _logStream.write(s.getBytes());
            }
            _logStream.flush();
            response.writeTo(_logStream);
            s = "******************\n\n";
            _logStream.write(s.getBytes());
        }
    }

    protected SOAPMessage readResponse(
        HttpURLConnection httpConnection,
        boolean isFailure,
        MimeHeaders headers)
        throws IOException, SOAPException {
        ByteInputStream in;
        InputStream contentIn =
            (isFailure
                ? httpConnection.getErrorStream()
                : httpConnection.getInputStream());

        byte[] bytes = readFully(contentIn);
        int length =
            httpConnection.getContentLength() == -1
                ? bytes.length
                : httpConnection.getContentLength();
        in = new ByteInputStream(bytes, length);

        // If in debug mode and we got HTML, print it out
        if (DEBUG) {
            if (httpConnection.getContentType().indexOf("text/html") >= 0) {
                System.out.println("");
                for (int i = 0; i < length; i++) {
                    System.out.print((char) bytes[i]);
                }
                System.out.println("");
            }
        }
        
        SOAPMessage response = _messageFactory.createMessage(headers, in);

        contentIn.close();

        return response;
    }

    protected MimeHeaders collectResponseMimeHeaders(HttpURLConnection httpConnection) {
        MimeHeaders headers = new MimeHeaders();
        for (int i = 1;; ++i) {
            String key = httpConnection.getHeaderFieldKey(i);
            if (key == null) {
                break;
            }
            String value = httpConnection.getHeaderField(i);
            try {
                headers.addHeader(key, value);
            } catch (IllegalArgumentException e) {
                // ignore headers that are illegal in MIME
            }
        }
        return headers;
    }

    protected boolean connectForResponse(
        HttpURLConnection httpConnection,
        SOAPMessageContext context)
        throws IOException {

        httpConnection.connect();
        return checkResponseCode(httpConnection, context);
    }

    protected void forceMessageToBeSent(
        HttpURLConnection httpConnection,
        SOAPMessageContext context)
        throws IOException {

        try {
            httpConnection.connect();
            httpConnection.getInputStream();
            checkResponseCode(httpConnection, context);

        } catch (IOException io) {
        }
    }

    /*
     * Will throw an exception instead of returning 'false' if there is no
     * return message to be processed (i.e., in the case of an UNAUTHORIZED
     * response from the servlet or 404 not found)
     */
    protected boolean checkResponseCode(
        HttpURLConnection httpConnection,
        SOAPMessageContext context)
        throws IOException {
        boolean isFailure = false;
        try {

            int statusCode = httpConnection.getResponseCode();
            context.setProperty(
                StubPropertyConstants.HTTP_STATUS_CODE,
                Integer.toString(statusCode));
            if ((httpConnection.getResponseCode()
                == HttpURLConnection.HTTP_INTERNAL_ERROR)) {
                isFailure = true;
                //added HTTP_ACCEPT for 1-way operations
            } else if (
                httpConnection.getResponseCode()
                    == HttpURLConnection.HTTP_UNAUTHORIZED) {

                // no soap message returned, so skip reading message and throw exception
                throw new ClientTransportException(
                    "http.client.unauthorized",
                    httpConnection.getResponseMessage());
            } else if (
                httpConnection.getResponseCode()
                    == HttpURLConnection.HTTP_NOT_FOUND) {

                // no message returned, so skip reading message and throw exception
                throw new ClientTransportException(
                    "http.not.found",
                    httpConnection.getResponseMessage());
            } else if (
                (statusCode == HttpURLConnection.HTTP_MOVED_TEMP) ||
                (statusCode == HttpURLConnection.HTTP_MOVED_PERM)){
                isFailure = true;

                if (!redirect || (redirectCount <=0)){
                throw new ClientTransportException(
                    "http.status.code",
                    new Object[] {
                        new Integer(statusCode),
                        getStatusMessage(httpConnection)});
                }
            } else if (
                statusCode < 200 || (statusCode >= 303 && statusCode < 500)) {
                throw new ClientTransportException(
                    "http.status.code",
                    new Object[] {
                        new Integer(statusCode),
                        getStatusMessage(httpConnection)});
            } else if (statusCode >= 500) {
                isFailure = true;
            }
        } catch (IOException e) {
            // on JDK1.3.1_01, we end up here, but then getResponseCode() succeeds!
            if (httpConnection.getResponseCode()
                == HttpURLConnection.HTTP_INTERNAL_ERROR) {
                isFailure = true;
            } else {
                throw e;
            }
        }

        return isFailure;

    }

    protected String getStatusMessage(HttpURLConnection httpConnection)
        throws IOException {
        int statusCode = httpConnection.getResponseCode();
        String message = httpConnection.getResponseMessage();
        if (statusCode == HttpURLConnection.HTTP_CREATED
            || (statusCode >= HttpURLConnection.HTTP_MULT_CHOICE
                && statusCode != HttpURLConnection.HTTP_NOT_MODIFIED
                && statusCode < HttpURLConnection.HTTP_BAD_REQUEST)) {
            String location = httpConnection.getHeaderField("Location");
            if (location != null)
                message += " - Location: " + location;
        }
        return message;
    }

    protected void logRequestMessage(SOAPMessageContext context)
        throws IOException, SOAPException {

        if (_logStream != null) {
            String s = "******************\nRequest\n";
            _logStream.write(s.getBytes());
            for (Iterator iter =
                context.getMessage().getMimeHeaders().getAllHeaders();
                iter.hasNext();
                ) {
                MimeHeader header = (MimeHeader) iter.next();
                s = header.getName() + ": " + header.getValue() + "\n";
                _logStream.write(s.getBytes());
            }
            _logStream.flush();
            context.getMessage().writeTo(_logStream);
            s = "\n";
            _logStream.write(s.getBytes());
            _logStream.flush();
        }
    }

    protected void writeMessageToConnection(
        SOAPMessageContext context,
        HttpURLConnection httpConnection)
        throws IOException, SOAPException {
        OutputStream contentOut = httpConnection.getOutputStream();
        context.getMessage().writeTo(contentOut);
        contentOut.flush();
        contentOut.close();
        logRequestMessage(context);
    }

    protected void moveHeadersFromContextToConnection(
        SOAPMessageContext context,
        HttpURLConnection httpConnection) {
        for (Iterator iter =
            context.getMessage().getMimeHeaders().getAllHeaders();
            iter.hasNext();
            ) {
            MimeHeader header = (MimeHeader) iter.next();
            httpConnection.setRequestProperty(
                header.getName(),
                header.getValue());
        }
    }

    protected CookieJar sendCookieAsNeeded(
        SOAPMessageContext context,
        HttpURLConnection httpConnection) {
        Boolean shouldMaintainSessionProperty =
            (Boolean) context.getProperty(SESSION_MAINTAIN_PROPERTY);
        boolean shouldMaintainSession =
            (shouldMaintainSessionProperty == null
                ? false
                : shouldMaintainSessionProperty.booleanValue());
        if (shouldMaintainSession) {
            CookieJar cookieJar =
                (CookieJar) context.getProperty(
                    StubPropertyConstants.HTTP_COOKIE_JAR);
            if (cookieJar == null) {
                cookieJar = new CookieJar();
            }
            cookieJar.applyRelevantCookies(httpConnection);
            return cookieJar;
        } else {
            return null;
        }
    }

    protected void saveCookieAsNeeded(
        SOAPMessageContext context,
        HttpURLConnection httpConnection,
        CookieJar cookieJar) {
        if (cookieJar != null) {
            cookieJar.recordAnyCookies(httpConnection);
            context.setProperty(
                StubPropertyConstants.HTTP_COOKIE_JAR,
                cookieJar);
        }
    }

    protected void setupContextForInvoke(SOAPMessageContext context)
        throws SOAPException, Exception {
        if (context.getMessage().saveRequired()) {
            context.getMessage().saveChanges();
        }
        String soapAction =
            (String) context.getProperty(HTTP_SOAPACTION_PROPERTY);
        // From SOAP 1.1 spec section 6.1.1 "The header field value of empty string ("") means that
        // the intent of the SOAP message is provided by the HTTP Request-URI. No value means that
        // there is no indication of the intent of the message." Here I provide a mechanism for
        // providing "no value" (PBG):
        //kw null soapaction? made not null-
        if (soapAction == null) {
            context.getMessage().getMimeHeaders().setHeader(
                "SOAPAction",
                "\"\"");
            // httpConnection.setRequestProperty("SOAPAction", "");
        } else {
            context.getMessage().getMimeHeaders().setHeader(
                "SOAPAction",
                "\"" + soapAction + "\"");
            // httpConnection.setRequestProperty("SOAPAction", "\"" + soapAction + "\"");
        }
        //set up Basic Authentication mime header
        String credentials = (String) context.getProperty(USERNAME_PROPERTY);
        if (credentials != null) {
            credentials += ":"
                + (String) context.getProperty(PASSWORD_PROPERTY);
            credentials =
                base64Encoder.objectToString(credentials.getBytes(), null);
            context.getMessage().getMimeHeaders().setHeader(
                "Authorization",
                "Basic " + credentials);
        }
    }

    protected HttpURLConnection createHttpConnection(
        String endpoint,
        SOAPMessageContext context)
        throws IOException {

        boolean verification = false;
        // does the client want client hostname verification by the service
        String verificationProperty =
            (String) context.getProperty(
                StubPropertyConstants.HOSTNAME_VERIFICATION_PROPERTY);
        if (verificationProperty != null) {
            if (verificationProperty.equalsIgnoreCase("true"))
                verification = true;
        }

         // does the client want request redirection to occur
        String redirectProperty =
            (String) context.getProperty(
                StubPropertyConstants.REDIRECT_REQUEST_PROPERTY);
        if (redirectProperty != null) {
            if (redirectProperty.equalsIgnoreCase("false"))
                redirect = false;
        }

        checkEndpoints(endpoint);

        HttpURLConnection httpConnection = createConnection(endpoint);

        if (!verification) {
            // for https hostname verification  - turn off by default
            if (httpConnection instanceof HttpsURLConnection) {
                ((HttpsURLConnection) httpConnection).setHostnameVerifier(
                    new HttpClientVerifier());
            }
        }

        // allow interaction with the web page - user may have to supply
        // username, password id web page is accessed from web browser
        httpConnection.setAllowUserInteraction(true);
        // enable input, output streams
        httpConnection.setDoOutput(true);
        httpConnection.setDoInput(true);
        // the soap message is always sent as a Http POST
        // HTTP Get is disallowed by BP 1.0
        httpConnection.setRequestMethod("POST");
        // Content type must be xml
        httpConnection.setRequestProperty("Content-Type", "text/xml");
        
        return httpConnection;
    }

    private java.net.HttpURLConnection createConnection(String endpoint)
        throws IOException {
        return (HttpURLConnection) new URL(endpoint).openConnection();
    }

    private void redirectRequest(HttpURLConnection httpConnection, SOAPMessageContext context){
        String redirectEndpoint = httpConnection.getHeaderField("Location");
        if (redirectEndpoint != null){
            httpConnection.disconnect();
            invoke(redirectEndpoint, context);
        }
        else System.out.println("redirection Failed");
    }

    private boolean checkForRedirect(int statusCode){
        return (((statusCode == 301) || (statusCode == 302)) && redirect && (redirectCount-- > 0));
    }

    private void checkEndpoints(String currentEndpoint){
        if (!LAST_ENDPOINT.equalsIgnoreCase(currentEndpoint)){
            redirectCount = START_REDIRECT_COUNT;
            LAST_ENDPOINT = currentEndpoint;
        }
    }

    private byte[] readFully(InputStream istream) throws IOException {
        if (istream == null)
            return new byte[0];
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int num = 0;
        while ((num = istream.read(buf)) != -1) {
            bout.write(buf, 0, num);
        }
        byte[] ret = bout.toByteArray();
        return ret;
    }

    private static void checkMessageContentType(String contentType, boolean response) {
        if (contentType.indexOf("text/html") >= 0) {
            System.out.println("##### WARNING " + 
                (response ? "RESPONSE" : "REQUEST") +
                " CONTENT TYPE INCLUDES 'text/html'");
            return;     // Allow HTML
        }
        
        System.out.println("##### CHECKING " +
            (response ? "RESPONSE" : "REQUEST") +
            " CONTENT TYPE '" + contentType + "'");
        
        String negotiation = 
            System.getProperty(com.sun.xml.rpc.client.StubPropertyConstants.CONTENT_NEGOTIATION_PROPERTY, "none").intern();
        
        // Use indexOf() to handle Multipart/related types
        if (negotiation == "none") {
            // OK only if XML
            if (contentType.indexOf("text/xml") < 0) {
                throw new RuntimeException("Invalid content type '" + contentType 
                    + "' in " + (response ? "response" : "request") + 
                    " with conneg set to '" + negotiation + "'.");
            }
        }
        else if (negotiation == "optimistic") {
            // OK only if FI
            if (contentType.indexOf("application/fastinfoset") < 0) {
                throw new RuntimeException("Invalid content type '" + contentType 
                    + "' in " + (response ? "response" : "request") + 
                    " with conneg set to '" + negotiation + "'.");
            }
        }
        else if (negotiation == "pessimistic") {
            // OK if FI request is anything and response is FI
            if (response && 
                    contentType.indexOf("application/fastinfoset") < 0) {
                throw new RuntimeException("Invalid content type '" + contentType 
                    + "' in " + (response ? "response" : "request") + 
                    " with conneg set to '" + negotiation + "'.");
            }
        }
    }
    
    // overide default SSL HttpClientVerifier to always return true
    // effectively overiding Hostname client verification when using SSL
    static class HttpClientVerifier implements HostnameVerifier {
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }
    }
    
    /**
     * Flag used to enable conneg content type check.
     */
    static private boolean DEBUG;
    static {
        final String value = System.getProperty("debug", "false");       
        DEBUG = value.equals("on") || value.equals("true");        
    }
    
    private MessageFactory _messageFactory;
    private OutputStream _logStream;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy