| /**************************************************************************** |
| * Copyright (c) 2009 EclipseSource and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * EclipseSource - initial API and implementation |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *****************************************************************************/ |
| package org.eclipse.ecf.remoteservice.rest.client; |
| |
| import java.io.*; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.*; |
| import org.apache.http.*; |
| import org.apache.http.auth.*; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.client.config.RequestConfig; |
| import org.apache.http.client.entity.UrlEncodedFormEntity; |
| import org.apache.http.client.methods.*; |
| import org.apache.http.client.utils.URIBuilder; |
| import org.apache.http.impl.auth.BasicScheme; |
| import org.apache.http.impl.client.HttpClientBuilder; |
| import org.apache.http.message.AbstractHttpMessage; |
| import org.apache.http.message.BasicNameValuePair; |
| import org.apache.http.protocol.BasicHttpContext; |
| import org.eclipse.ecf.core.security.*; |
| import org.eclipse.ecf.core.util.ECFException; |
| import org.eclipse.ecf.remoteservice.IRemoteCall; |
| import org.eclipse.ecf.remoteservice.IRemoteService; |
| import org.eclipse.ecf.remoteservice.client.*; |
| import org.eclipse.ecf.remoteservice.rest.IRestCall; |
| import org.eclipse.ecf.remoteservice.rest.RestException; |
| |
| /** |
| * This class represents a REST service from the client side of view. So a |
| * RESTful web service can be accessed via the methods provided by this class. |
| * Mostly the methods are inherited from {@link IRemoteService}. |
| */ |
| public class RestClientService extends AbstractRestClientService { |
| |
| public static final int socketTimeout = Integer.parseInt(System.getProperty("org.eclipse.ecf.remoteservice.rest.RestClientService.socketTimeout", "-1")); //$NON-NLS-1$ //$NON-NLS-2$ |
| public static final int connectRequestTimeout = Integer.parseInt(System.getProperty("org.eclipse.ecf.remoteservice.rest.RestClientService.connectRequestTimeout", "-1")); //$NON-NLS-1$ //$NON-NLS-2$ |
| public static final int connectTimeout = Integer.parseInt(System.getProperty("org.eclipse.ecf.remoteservice.rest.RestClientService.connectTimeout", "-1")); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| protected final static int DEFAULT_RESPONSE_BUFFER_SIZE = 1024; |
| |
| protected final static String DEFAULT_HTTP_CONTENT_CHARSET = "UTF-8"; //$NON-NLS-1$ |
| |
| protected HttpClient httpClient; |
| protected int responseBufferSize = DEFAULT_RESPONSE_BUFFER_SIZE; |
| |
| public RestClientService(RestClientContainer container, RemoteServiceClientRegistration registration) { |
| super(container, registration); |
| this.httpClient = createHttpClient(); |
| } |
| |
| protected HttpClient createHttpClient() { |
| return HttpClientBuilder.create().build(); |
| } |
| |
| private boolean isResponseOk(HttpResponse response) { |
| int isOkCode = response.getStatusLine().getStatusCode() - 200; |
| return (isOkCode >= 0 && isOkCode < 100); |
| } |
| |
| protected HttpGet createGetMethod(String uri) { |
| return new HttpGet(uri); |
| } |
| |
| protected HttpPost createPostMethod(String uri) { |
| return new HttpPost(uri); |
| } |
| |
| protected HttpPut createPutMethod(String uri) { |
| return new HttpPut(uri); |
| } |
| |
| protected HttpDelete createDeleteMethod(String uri) { |
| return new HttpDelete(uri); |
| } |
| |
| /** |
| * @since 2.6 |
| */ |
| protected HttpPatch createPatchMethod(String uri) { |
| return new HttpPatch(uri); |
| } |
| |
| protected HttpRequestBase createAndPrepareHttpMethod(UriRequest request) { |
| HttpRequestBase httpMethod = null; |
| String uri = request.getUri(); |
| final IRemoteCallable callable = request.getRemoteCallable(); |
| IRemoteCallableRequestType requestType = (callable == null) ? new HttpGetRequestType() : callable.getRequestType(); |
| if (requestType instanceof HttpGetRequestType) |
| httpMethod = createGetMethod(uri); |
| else if (requestType instanceof HttpPatchRequestType) |
| httpMethod = createPatchMethod(uri); |
| else if (requestType instanceof HttpPostRequestType) |
| httpMethod = createPostMethod(uri); |
| else if (requestType instanceof HttpPutRequestType) |
| httpMethod = createPutMethod(uri); |
| else if (requestType instanceof HttpDeleteRequestType) |
| httpMethod = createDeleteMethod(uri); |
| // all prepare HttpMethod |
| prepareHttpMethod(httpMethod, request.getRemoteCall(), callable); |
| return httpMethod; |
| } |
| |
| /** |
| * Calls the Rest service with given URL of IRestCall. The returned value is |
| * the response body as an InputStream. |
| * |
| * @param call |
| * The remote call to make. Must not be <code>null</code>. |
| * @param callable |
| * The callable with default parameters to use to make the call. |
| * @return The InputStream of the response body or <code>null</code> if an |
| * error occurs. |
| */ |
| protected Object invokeRemoteCall(final IRemoteCall call, final IRemoteCallable callable) throws ECFException { |
| trace("invokeRemoteCall", "call=" + call + ";callable=" + callable); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| String endpointUri = prepareEndpointAddress(call, callable); |
| trace("invokeRemoteCall", "prepared endpoint=" + endpointUri); //$NON-NLS-1$ //$NON-NLS-2$ |
| UriRequest urirequest = createUriRequest(endpointUri, call, callable); |
| // If the request |
| HttpRequestBase httpMethod = (urirequest == null) ? createAndPrepareHttpMethod(endpointUri, call, callable) : createAndPrepareHttpMethod(urirequest); |
| trace("invokeRemoteCall", "executing httpMethod" + httpMethod); //$NON-NLS-1$ //$NON-NLS-2$ |
| // execute method |
| byte[] responseBody = null; |
| int responseCode = 500; |
| HttpResponse response = null; |
| try { |
| response = httpClient.execute(httpMethod); |
| trace("invokeRemoteCall", "httpMethod executed. response=" + response); //$NON-NLS-1$ //$NON-NLS-2$ |
| responseCode = response.getStatusLine().getStatusCode(); |
| if (isResponseOk(response)) { |
| // Get responseBody as String |
| responseBody = getResponseAsBytes(response); |
| } else { |
| // If this method returns true, we should retrieve the response body |
| if (retrieveErrorResponseBody(response)) { |
| responseBody = getResponseAsBytes(response); |
| } |
| // Now pass to the exception handler |
| handleException("Http response not OK. httpMethod=" + httpMethod + " responseCode=" + Integer.valueOf(responseCode), null, responseCode, responseBody); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } catch (IOException e) { |
| handleException("RestClientService transport IOException", e, responseCode); //$NON-NLS-1$ |
| } |
| Object result = null; |
| try { |
| Map responseHeaders = convertResponseHeaders(response.getAllHeaders()); |
| trace("processResponse", "httpMethod=" + httpMethod + ";call=" + call + ";callable=" + callable + ";responseHeaders=" + responseHeaders + ";responseBody=" + responseBody); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ |
| result = processResponse(endpointUri, call, callable, responseHeaders, responseBody); |
| } catch (NotSerializableException e) { |
| handleException("Exception deserializing response. httpMethod=" + httpMethod + " responseCode=" + Integer.valueOf(responseCode), e, responseCode); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return result; |
| } |
| |
| protected boolean retrieveErrorResponseBody(HttpResponse response) { |
| // XXX this needs to be defined differently for |
| return false; |
| } |
| |
| protected byte[] getResponseAsBytes(HttpResponse response) throws IOException { |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| response.getEntity().writeTo(os); |
| return os.toByteArray(); |
| } |
| |
| /* |
| * @deprecated |
| */ |
| protected void setupTimeouts(HttpClient httpClient, IRemoteCall call, IRemoteCallable callable) { |
| // No longer used |
| } |
| |
| private Map convertResponseHeaders(Header[] headers) { |
| Map result = new HashMap(); |
| if (headers == null) |
| return result; |
| for (int i = 0; i < headers.length; i++) { |
| String name = headers[i].getName(); |
| String value = headers[i].getValue(); |
| result.put(name, value); |
| } |
| return result; |
| } |
| |
| protected void addRequestHeaders(AbstractHttpMessage httpMethod, IRemoteCall call, IRemoteCallable callable) { |
| // Add request headers from the callable |
| Map requestHeaders = (callable.getRequestType() instanceof AbstractRequestType) ? ((AbstractRequestType) callable.getRequestType()).getDefaultRequestHeaders() : new HashMap(); |
| if (requestHeaders == null) |
| requestHeaders = new HashMap(); |
| |
| if (call instanceof IRestCall) { |
| Map callHeaders = ((IRestCall) call).getRequestHeaders(); |
| if (callHeaders != null) |
| requestHeaders.putAll(requestHeaders); |
| } |
| |
| Set keySet = requestHeaders.keySet(); |
| Object[] headers = keySet.toArray(); |
| for (int i = 0; i < headers.length; i++) { |
| String key = (String) headers[i]; |
| String value = (String) requestHeaders.get(key); |
| httpMethod.addHeader(key, value); |
| } |
| } |
| |
| protected HttpRequestBase createAndPrepareHttpMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws RestException { |
| HttpRequestBase httpMethod = null; |
| |
| IRemoteCallableRequestType requestType = callable.getRequestType(); |
| if (requestType == null) |
| throw new RestException("Request type for call cannot be null"); //$NON-NLS-1$ |
| try { |
| if (requestType instanceof HttpGetRequestType) { |
| httpMethod = prepareGetMethod(uri, call, callable); |
| } else if (requestType instanceof HttpPatchRequestType) { |
| httpMethod = preparePatchMethod(uri, call, callable); |
| } else if (requestType instanceof HttpPostRequestType) { |
| httpMethod = preparePostMethod(uri, call, callable); |
| } else if (requestType instanceof HttpPutRequestType) { |
| httpMethod = preparePutMethod(uri, call, callable); |
| } else if (requestType instanceof HttpDeleteRequestType) { |
| httpMethod = prepareDeleteMethod(uri, call, callable); |
| } else { |
| throw new RestException("HTTP method " + requestType + " not supported"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } catch (NotSerializableException e) { |
| String message = "Could not serialize parameters for uri=" + uri + " call=" + call + " callable=" + callable; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| logException(message, e); |
| throw new RestException(message); |
| } catch (UnsupportedEncodingException e) { |
| String message = "Could not serialize parameters for uri=" + uri + " call=" + call + " callable=" + callable; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| logException(message, e); |
| throw new RestException(message); |
| } |
| |
| prepareHttpMethod(httpMethod, call, callable); |
| return httpMethod; |
| } |
| |
| protected void prepareHttpMethod(HttpRequestBase httpMethod, IRemoteCall call, IRemoteCallable callable) { |
| // add additional request headers |
| addRequestHeaders(httpMethod, call, callable); |
| // handle authentication |
| setupAuthenticaton(httpClient, httpMethod); |
| // setup http method config (redirects, timeouts, etc) |
| setupHttpMethod(httpMethod, call, callable); |
| } |
| |
| protected void setupHttpMethod(HttpRequestBase httpMethod, IRemoteCall call, IRemoteCallable callable) { |
| |
| RequestConfig defaultRequestConfig = httpMethod.getConfig(); |
| RequestConfig.Builder updatedRequestConfigBuilder = (defaultRequestConfig == null) ? RequestConfig.custom() : RequestConfig.copy(defaultRequestConfig); |
| // setup to allow regular and circular redirects |
| updatedRequestConfigBuilder.setCircularRedirectsAllowed(true); |
| updatedRequestConfigBuilder.setRedirectsEnabled(true); |
| |
| int sTimeout = socketTimeout; |
| int scTimeout = connectTimeout; |
| int scrTimeout = connectRequestTimeout; |
| |
| long callTimeout = call.getTimeout(); |
| if (callTimeout == IRemoteCall.DEFAULT_TIMEOUT) |
| callTimeout = callable.getDefaultTimeout(); |
| |
| if (callTimeout != IRemoteCall.DEFAULT_TIMEOUT) { |
| sTimeout = scTimeout = scrTimeout = new Long(callTimeout).intValue(); |
| } |
| updatedRequestConfigBuilder.setSocketTimeout(sTimeout); |
| updatedRequestConfigBuilder.setConnectTimeout(scTimeout); |
| updatedRequestConfigBuilder.setConnectionRequestTimeout(scrTimeout); |
| |
| httpMethod.setConfig(updatedRequestConfigBuilder.build()); |
| } |
| |
| /** |
| * @throws RestException |
| */ |
| protected HttpRequestBase prepareDeleteMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws RestException { |
| return new HttpDelete(uri); |
| } |
| |
| protected HttpRequestBase preparePutMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws NotSerializableException, UnsupportedEncodingException { |
| HttpPut result = new HttpPut(uri); |
| HttpPutRequestType putRequestType = (HttpPutRequestType) callable.getRequestType(); |
| |
| IRemoteCallParameter[] defaultParameters = callable.getDefaultParameters(); |
| Object[] parameters = call.getParameters(); |
| |
| if (putRequestType.useRequestEntity()) { |
| if (defaultParameters != null && defaultParameters.length > 0 && parameters != null && parameters.length > 0) { |
| HttpEntity requestEntity = putRequestType.generateRequestEntity(uri, call, callable, defaultParameters[0], parameters[0]); |
| result.setEntity(requestEntity); |
| } |
| } else { |
| NameValuePair[] params = toNameValuePairs(uri, call, callable); |
| if (params != null) { |
| result.setEntity(getUrlEncodedFormEntity(Arrays.asList(params), putRequestType)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @throws UnsupportedEncodingException |
| * @throws ECFException |
| * @since 2.6 |
| */ |
| protected HttpRequestBase preparePatchMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws NotSerializableException, UnsupportedEncodingException { |
| HttpPatch result = new HttpPatch(uri); |
| HttpPostRequestType postRequestType = (HttpPostRequestType) callable.getRequestType(); |
| |
| IRemoteCallParameter[] defaultParameters = callable.getDefaultParameters(); |
| Object[] parameters = call.getParameters(); |
| if (postRequestType.useRequestEntity()) { |
| if (defaultParameters != null && defaultParameters.length > 0 && parameters != null && parameters.length > 0) { |
| HttpEntity requestEntity = postRequestType.generateRequestEntity(uri, call, callable, defaultParameters[0], parameters[0]); |
| result.setEntity(requestEntity); |
| } |
| } else { |
| NameValuePair[] params = toNameValuePairs(uri, call, callable); |
| if (params != null) { |
| result.setEntity(getUrlEncodedFormEntity(Arrays.asList(params), postRequestType)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @throws UnsupportedEncodingException |
| * @throws ECFException |
| */ |
| protected HttpRequestBase preparePostMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws NotSerializableException, UnsupportedEncodingException { |
| HttpPost result = new HttpPost(uri); |
| HttpPostRequestType postRequestType = (HttpPostRequestType) callable.getRequestType(); |
| |
| IRemoteCallParameter[] defaultParameters = callable.getDefaultParameters(); |
| Object[] parameters = call.getParameters(); |
| if (postRequestType.useRequestEntity()) { |
| if (defaultParameters != null && defaultParameters.length > 0 && parameters != null && parameters.length > 0) { |
| HttpEntity requestEntity = postRequestType.generateRequestEntity(uri, call, callable, defaultParameters[0], parameters[0]); |
| result.setEntity(requestEntity); |
| } |
| } else { |
| NameValuePair[] params = toNameValuePairs(uri, call, callable); |
| if (params != null) { |
| result.setEntity(getUrlEncodedFormEntity(Arrays.asList(params), postRequestType)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @throws NotSerializableException |
| * @throws ECFException |
| */ |
| protected HttpRequestBase prepareGetMethod(String uri, IRemoteCall call, IRemoteCallable callable) throws NotSerializableException { |
| NameValuePair[] params = toNameValuePairs(uri, call, callable); |
| URI httpURI = null; |
| try { |
| URIBuilder builder = new URIBuilder(uri); |
| if (params != null) |
| for (int i = 0; i < params.length; i++) |
| builder.addParameter(params[i].getName(), params[i].getValue()); |
| httpURI = builder.build(); |
| } catch (URISyntaxException e1) { |
| throw new NotSerializableException("uri=" + uri + " does not have proper URI syntax"); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| return new HttpGet(httpURI); |
| } |
| |
| protected UrlEncodedFormEntity getUrlEncodedFormEntity(List list, AbstractEntityRequestType postRequestType) throws UnsupportedEncodingException { |
| if (postRequestType.defaultCharset != null) { |
| return new UrlEncodedFormEntity(list, postRequestType.defaultCharset); |
| } |
| return new UrlEncodedFormEntity(list); |
| } |
| |
| protected NameValuePair[] toNameValuePairs(String uri, IRemoteCall call, IRemoteCallable callable) throws NotSerializableException { |
| IRemoteCallParameter[] restParameters = prepareParameters(uri, call, callable); |
| List nameValueList = new ArrayList(); |
| if (restParameters != null) { |
| for (int i = 0; i < restParameters.length; i++) { |
| String parameterValue = null; |
| Object o = restParameters[i].getValue(); |
| if (o instanceof String) { |
| parameterValue = (String) o; |
| } else if (o != null) { |
| parameterValue = o.toString(); |
| } |
| if (parameterValue != null) { |
| nameValueList.add(new BasicNameValuePair(restParameters[i].getName(), parameterValue)); |
| } |
| } |
| } |
| return (NameValuePair[]) nameValueList.toArray(new NameValuePair[nameValueList.size()]); |
| } |
| |
| protected void setupAuthenticaton(HttpClient httpClient, HttpRequestBase method) { |
| IConnectContext connectContext = container.getConnectContextForAuthentication(); |
| if (connectContext != null) { |
| NameCallback nameCallback = new NameCallback(""); //$NON-NLS-1$ |
| ObjectCallback passwordCallback = new ObjectCallback(); |
| Callback[] callbacks = new Callback[] {nameCallback, passwordCallback}; |
| CallbackHandler callbackHandler = connectContext.getCallbackHandler(); |
| if (callbackHandler == null) |
| return; |
| try { |
| callbackHandler.handle(callbacks); |
| String username = nameCallback.getName(); |
| String password = (String) passwordCallback.getObject(); |
| Credentials credentials = new UsernamePasswordCredentials(username, password); |
| method.addHeader(new BasicScheme().authenticate(credentials, method, new BasicHttpContext())); |
| } catch (IOException e) { |
| logException("IOException setting credentials for rest httpclient", e); //$NON-NLS-1$ |
| } catch (UnsupportedCallbackException e) { |
| logException("UnsupportedCallbackException setting credentials for rest httpclient", e); //$NON-NLS-1$ |
| } catch (AuthenticationException e) { |
| logException("AuthenticationException setting credentials for rest httpclient", e); //$NON-NLS-1$ |
| } |
| |
| } |
| } |
| |
| } |