Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OkHttp client #416

Merged
merged 26 commits into from
Aug 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1d4c140
Created stub factory, client and test using jaxrs as a template
Jul 18, 2016
e563079
OkHttp client, request and response partially implemented
Jul 20, 2016
398ac63
OkHttp client now adds sensible default headers to requests
Jul 20, 2016
a276033
Creating an OkHttp byte request is now implemented
Jul 20, 2016
09fe878
OkHttp query parameter requests now supported
Jul 20, 2016
e7401d2
Refactored tests, no longer comparing case for charsets
Jul 20, 2016
ce01776
Added proxy support to okhttpclientfactory
Jul 21, 2016
053ed40
Fixed no-context test - expected exception message is now generic
Jul 21, 2016
dd0a88b
fixed some content type comparisons
Jul 21, 2016
82931f5
Removing trailing question marks from URLs as OkHttp doesn't do this …
Jul 21, 2016
0870ab6
Fixed NPE when content-type header doesn't exist
Jul 21, 2016
7ed183b
Correct HTTP method is now set on OkHttp requests (PUT, POST, UPDATE,…
Jul 21, 2016
b4851e3
Removed support for getRequestBodyFromStream - wasn't implemented cor…
Jul 21, 2016
b0356ff
Added response buffering
Jul 21, 2016
b325e79
Merge branch 'master' into okhttp
Jul 25, 2016
b52a849
Updated test in light of changes pulled in from master
Jul 25, 2016
b2f57d0
Extract method refactors in okhttprestfulclient
Jul 26, 2016
03dd6d7
Implemented binary request with the OkHttp client
Jul 27, 2016
0134eab
Extracted out a generic string utils class
Jul 27, 2016
461046d
Added relevant file and class headers
Jul 27, 2016
79d54c6
Reformatted code
Jul 27, 2016
394dcc1
Implemented getHttpClient(serverBase) in OkHttpRestfulClientFactory
Jul 28, 2016
991585a
Renamed fields and parameters to be consistent with the rest of the p…
Jul 28, 2016
415f8e5
Reformat OkHttpClientDstu2Test (removes extraneous blank lines etc.)
Aug 1, 2016
d6810b3
Removed additional blank lines in test
Aug 2, 2016
835654c
Copied missing bundle_orion.xml to OkHttp test resources
Aug 2, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions hapi-fhir-okhttp/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>hapi-fhir-okhttp</artifactId>
<packaging>jar</packaging>

<name>HAPI FHIR OkHttp Client</name>

<dependencies>
<!-- HAPI DEPENDENCIES -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>2.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- conformance profile -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.1</version>
</dependency>

<!-- Unit test dependencies -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-jetty-http</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package ca.uhn.fhir.okhttp.client;

/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.HttpClientUtil;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import okhttp3.*;
import okhttp3.internal.Version;
import org.hl7.fhir.instance.model.api.IBaseBinary;

import java.util.List;
import java.util.Map;

import static ca.uhn.fhir.okhttp.utils.UrlStringUtils.*;

/**
* A Http Request based on OkHttp. This is an adapter around the class
* {@link OkHttpClient}
*
* @author Matthew Clarke | matthew.clarke@orionhealth.com | Orion Health
*/
public class OkHttpRestfulClient implements IHttpClient {

private OkHttpClient myClient;
private StringBuilder myUrl;
private Map<String, List<String>> myIfNoneExistParams;
private String myIfNoneExistString;
private RequestTypeEnum myRequestType;
private List<Header> myHeaders;
private OkHttpRestfulRequest myRequest;

public OkHttpRestfulClient(OkHttpClient theClient,
StringBuilder theUrl,
Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString,
RequestTypeEnum theRequestType,
List<Header> theHeaders) {
myClient = theClient;
myUrl = theUrl;
myIfNoneExistParams = theIfNoneExistParams;
myIfNoneExistString = theIfNoneExistString;
myRequestType = theRequestType;
myHeaders = theHeaders;
}

@Override
public IHttpRequest createByteRequest(FhirContext theContext, String theContents, String theContentType, EncodingEnum theEncoding) {
initBaseRequest(theContext, theEncoding, createPostBody(theContents, theContentType));
return myRequest;
}

private void initBaseRequest(FhirContext theContext, EncodingEnum theEncoding, RequestBody body) {
String sanitisedUrl = withTrailingQuestionMarkRemoved(myUrl.toString());
myRequest = new OkHttpRestfulRequest(myClient, sanitisedUrl, myRequestType, body);
addHeadersToRequest(myRequest, theEncoding, theContext);
}

private RequestBody createPostBody(String theContents, String theContentType) {
return RequestBody.create(MediaType.parse(theContentType), theContents);
}

@Override
public IHttpRequest createParamRequest(FhirContext theContext, Map<String, List<String>> theParams, EncodingEnum theEncoding) {
initBaseRequest(theContext, theEncoding, getFormBodyFromParams(theParams));
return myRequest;
}

private RequestBody getFormBodyFromParams(Map<String, List<String>> queryParams) {
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry<String, List<String>> paramEntry : queryParams.entrySet()) {
for (String value : paramEntry.getValue()) {
formBuilder.add(paramEntry.getKey(), value);
}
}

return formBuilder.build();
}

@Override
public IHttpRequest createBinaryRequest(FhirContext theContext, IBaseBinary theBinary) {
initBaseRequest(theContext, null, createPostBody(theBinary.getContent(), theBinary.getContentType()));
return myRequest;
}

private RequestBody createPostBody(byte[] theContents, String theContentType) {
return RequestBody.create(MediaType.parse(theContentType), theContents);
}

@Override
public IHttpRequest createGetRequest(FhirContext theContext, EncodingEnum theEncoding) {
initBaseRequest(theContext, theEncoding, null);
return myRequest;
}

private void addHeadersToRequest(OkHttpRestfulRequest theHttpRequest, EncodingEnum theEncoding, FhirContext theContext) {
if (myHeaders != null) {
for (Header next : myHeaders) {
theHttpRequest.addHeader(next.getName(), next.getValue());
}
}

addUserAgentHeader(theHttpRequest, theContext);
addAcceptCharsetHeader(theHttpRequest);
addAcceptHeader(theHttpRequest, theEncoding);
addIfNoneExistHeader(theHttpRequest);
}

private void addUserAgentHeader(OkHttpRestfulRequest theHttpRequest, FhirContext theContext) {
theHttpRequest.addHeader("User-Agent", HttpClientUtil.createUserAgentString(theContext, Version.userAgent()));
}

private void addAcceptCharsetHeader(OkHttpRestfulRequest theHttpRequest) {
theHttpRequest.addHeader("Accept-Charset", "utf-8");
}

private void addAcceptHeader(OkHttpRestfulRequest theHttpRequest, EncodingEnum theEncoding) {
Request.Builder builder = theHttpRequest.getRequest();

if (theEncoding == null) {
builder.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_LEGACY);
} else if (theEncoding == EncodingEnum.JSON) {
builder.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON);
} else if (theEncoding == EncodingEnum.XML) {
builder.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML);
}
}

private void addIfNoneExistHeader(IHttpRequest result) {
if (myIfNoneExistParams != null) {
addIfNoneExistHeaderFromParams(result, myIfNoneExistParams);
} else if (myIfNoneExistString != null) {
addIfNoneExistHeaderFromString(result, myIfNoneExistString);
}
}

private void addIfNoneExistHeaderFromString(IHttpRequest result, String ifNoneExistString) {
StringBuilder sb = newHeaderBuilder(myUrl);
boolean shouldAddQuestionMark = !hasQuestionMark(sb);
sb.append(shouldAddQuestionMark ? '?' : '&');
sb.append(everythingAfterFirstQuestionMark(ifNoneExistString));
result.addHeader(Constants.HEADER_IF_NONE_EXIST, sb.toString());
}

private void addIfNoneExistHeaderFromParams(IHttpRequest result, Map<String, List<String>> ifNoneExistParams) {
StringBuilder sb = newHeaderBuilder(myUrl);
boolean shouldAddInitialQuestionMark = !hasQuestionMark(sb);
BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(ifNoneExistParams, sb, shouldAddInitialQuestionMark);
result.addHeader(Constants.HEADER_IF_NONE_EXIST, sb.toString());
}

public static StringBuilder newHeaderBuilder(StringBuilder baseUrl) {
StringBuilder sb = new StringBuilder(baseUrl);
if (endsWith(baseUrl, '/')) {
deleteLastCharacter(sb);
}
return sb;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ca.uhn.fhir.okhttp.client;

/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.RestfulClientFactory;
import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import okhttp3.OkHttpClient;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.Map;

/**
* A Restful client factory based on OkHttp.
*
* @author Matthew Clarke | matthew.clarke@orionhealth.com | Orion Health
*/
public class OkHttpRestfulClientFactory extends RestfulClientFactory {

private OkHttpClient myNativeClient;

public OkHttpRestfulClientFactory() {
super();
}

public OkHttpRestfulClientFactory(FhirContext theFhirContext) {
super(theFhirContext);
}

@Override
protected IHttpClient getHttpClient(String theServerBase) {
return new OkHttpRestfulClient(getNativeClient(), new StringBuilder(theServerBase), null, null, null, null);
}

@Override
protected void resetHttpClient() {
myNativeClient = null;
}

public synchronized OkHttpClient getNativeClient() {
if (myNativeClient == null) {
myNativeClient = new OkHttpClient();
}

return myNativeClient;
}

@Override
public IHttpClient getHttpClient(StringBuilder theUrl,
Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString,
RequestTypeEnum theRequestType,
List<Header> theHeaders) {
return new OkHttpRestfulClient(getNativeClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
}

/**
* Only accepts clients of type {@link OkHttpClient}
*
* @param okHttpClient
*/
@Override
public void setHttpClient(Object okHttpClient) {
myNativeClient = (OkHttpClient) okHttpClient;
}

@Override
public void setProxy(String theHost, Integer thePort) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(theHost, thePort));
OkHttpClient.Builder builder = getNativeClient().newBuilder().proxy(proxy);
setHttpClient(builder.build());
}

}
Loading