├── README.md ├── build.sbt ├── project ├── bintray.sbt ├── build.properties └── plugins.sbt └── src ├── main └── java │ └── http │ └── rest │ ├── AbstractRestClient.java │ ├── RequestInterceptor.java │ ├── RestClient.java │ ├── RestClientBuilder.java │ └── RestClientException.java └── test ├── java └── test │ ├── Assertions.java │ ├── Settings.java │ ├── integration │ ├── CustomRestClientTest.java │ ├── GoogleGeocodeTest.java │ └── RequestInterceptorTest.java │ └── unit │ ├── BaseTest.java │ └── TestRestClient.java └── resources └── geocode.json /README.md: -------------------------------------------------------------------------------- 1 | http-rest-client 2 | ====================== 3 | 4 | A simple & easy to use REST client written in Java and levarging the HttpClient 4.3 library. 5 | 6 | Important Note: This library only supports JSON payloads. You are more than welcome to add XML support and send me a pull request. I have no future plans to add XML support. 7 | 8 | Cheers! 9 | 10 | SBT / Maven Dependency 11 | ------------------------- 12 | 13 | SBT 14 | 15 | resolvers += "java-utils" at "http://dl.bintray.com/g00dnatur3/java-utils/" 16 | 17 | libraryDependencies ++= Seq( 18 | "g00dnatur3" %% "http-rest-client" % "1.0.21" 19 | ) 20 | 21 | Maven 22 | 23 | 24 | java-utils 25 | http://dl.bintray.com/g00dnatur3/java-utils/ 26 | 27 | 28 | 29 | g00dnatur3 30 | http-rest-client_2.10 31 | 1.0.21 32 | 33 | 34 | Getting Started 35 | ------------------------- 36 | 37 | This is the simplest way to construct a RestClient 38 | 39 | RestClient client = RestClient.builder().build(); 40 | 41 | 42 | This example executes a GET request to the google geocoder api: 43 | 44 | RestClient client = RestClient.builder().build(); 45 | 46 | String geocoderUrl = "http://maps.googleapis.com/maps/api/geocode/json"; 47 | 48 | Map params = Maps.newHashMap(); 49 | params.put("address", "1980 W. Bayshore Rd. 94303"); 50 | params.put("sensor", "false"); 51 | 52 | JsonNode node = client.get(geocoderUrl, params, JsonNode.class); 53 | 54 | Execute a GET Request to get a single object 55 | ------------------------------------------------------ 56 | 57 | String url = ... 58 | Map queryParams = ... 59 | 60 | Person person = client.get(url, queryParams, Person.class); 61 | 62 | Execute a GET Request to get a list of objects 63 | ------------------------------------------------------ 64 | 65 | String url = ... 66 | Map queryParams = ... 67 | 68 | List people = client.get(url, queryParams, new TypeReference>() {}); 69 | 70 | Execute a POST Request on a single object 71 | ---------------------------------------------- 72 | 73 | String url = ... 74 | Person person = ... 75 | 76 | Header header = client.create(url, person); 77 | 78 | if (header != null) { 79 | System.out.println("Location header is: " + header.value()); 80 | } 81 | 82 | Execute a POST Request on a list of objects 83 | ---------------------------------------------- 84 | 85 | String url = ... 86 | List people = ... 87 | 88 | Header header = client.create(url, people); 89 | 90 | if (header != null) { 91 | System.out.println("Location header is: " + header.value()); 92 | } 93 | 94 | Execute a PUT Request on a single object 95 | --------------------------------------------- 96 | 97 | String url = ... 98 | Person person = ... 99 | 100 | client.update(url, person); 101 | 102 | Execute a PUT Request on a list of objects 103 | --------------------------------------------- 104 | 105 | String url = ... 106 | List people = ... 107 | 108 | client.update(url, people); 109 | 110 | Add Headers with RequestInterceptor 111 | ------------------------------------------------ 112 | 113 | In order to add headers on a request you need to use the RequestInterceptor. 114 | 115 | This is an example of a RequestInterceptor that will "intercept" all requests sent from a RestClient and add an Authorization header to them. 116 | 117 | final String credentials = ... 118 | RequestInterceptor authorize = new RequestInterceptor() { 119 | @Override 120 | public void intercept(HttpRequestBase request) { 121 | request.addHeader("Authorization", credentials); 122 | } 123 | }; 124 | RestClient client = RestClient.builder().requestInterceptor(authorize).build(); 125 | 126 | All the `get`,`create`,`update`,`delete` methods are overloaded with a RequestInterceptor. 127 | 128 | This example of RequestInterceptor will "intercept" on a per request basis to add a header: 129 | 130 | public RequestInterceptor authorize(final String credentials) { 131 | return new RequestInterceptor() { 132 | @Override 133 | public void intercept(HttpUriRequest request) { 134 | request.addHeader("Authorization", credentials); 135 | } 136 | }; 137 | } 138 | 139 | public Person getPerson(String credentials, url) { 140 | return client.get(authorize(credentials), url, null, Person.class); //queryParams=null 141 | } 142 | 143 | public Header createPerson(String credentials, Person person, url) { 144 | return client.create(authorize(credentials), url, person); 145 | } 146 | 147 | Look at `test.integration.RequestInterceptorTest` to see working examples. 148 | 149 | Configure the Request with RequestInterceptor 150 | ---------------------------------------------- 151 | 152 | Similar with how we can add headers on a per request basis, we can also do some config on a per request basis. 153 | 154 | public RequestInterceptor connectTimeout(final int timeout) { 155 | return new RequestInterceptor() { 156 | @Override 157 | public void intercept(HttpUriRequest request) { 158 | request.setConfig( 159 | RequestConfig.custom() 160 | .setConnectTimeout(timeout).build()); 161 | } 162 | }; 163 | } 164 | 165 | client.create(connectTimeout(1000), url, person); 166 | 167 | 168 | Configuration (Proxy, SSL...) 169 | ------------------------- 170 | 171 | The following system properties are taken into account by this library: 172 | 173 | ssl.TrustManagerFactory.algorithm 174 | javax.net.ssl.trustStoreType 175 | javax.net.ssl.trustStore 176 | javax.net.ssl.trustStoreProvider 177 | javax.net.ssl.trustStorePassword 178 | java.home 179 | ssl.KeyManagerFactory.algorithm 180 | javax.net.ssl.keyStoreType 181 | javax.net.ssl.keyStore 182 | javax.net.ssl.keyStoreProvider 183 | javax.net.ssl.keyStorePassword 184 | http.proxyHost 185 | http.proxyPort 186 | http.nonProxyHosts 187 | http.keepAlive 188 | http.maxConnections 189 | 190 | For example, if you wanted to configure the proxy you could do: 191 | 192 | System.setProperty("http.proxyHost","somehost.com") 193 | System.setProperty("http.proxyPort","8080") 194 | 195 | Handling failed Requests 196 | ------------------------------ 197 | If your request fails, have no fear, you can get your hands on the HttpResonse and handle it however you need. 198 | 199 | The `get`,`create`,`update`,`delete` methods throw a RestClientException when they fail to execute. 200 | 201 | The RestClientException contains the actual "ready-to-consume" HttpResponse object. 202 | 203 | try { 204 | Person p = client.get(url, queryParams, Person.class) 205 | } catch (RestClientException e) { 206 | 207 | HttpResponse response = e.response(); 208 | if (response != null) { 209 | 210 | // if the payload contains error information you can get it 211 | String errorInfo = client.contentAsString(response); 212 | 213 | client.consume(response); //closes the response 214 | } 215 | } 216 | 217 | Expected Response Status 218 | ------------------------------ 219 | 220 | The default expected response status for `get`,`update`,`delete` is 200 and for `create` it's 201. 221 | 222 | The RestClient will throw a RestClientException if the actual response status is not equal to the expected status. 223 | 224 | All the methods in the RestClient are overloaded with `int expectedStatus` so you can specify the expected response status if it deviates from the default. 225 | 226 | For example, the `create` has a default expected status of 201. However, if the server sends back a 200 response instead, you can do the following: 227 | 228 | String url = ... 229 | Person person = ... 230 | 231 | try { 232 | Header header = client.create(url, person, 200); 233 | } catch (RestClientException e) { 234 | // here if the response status is not 200 or the request failed to execute. 235 | } 236 | 237 | 238 | Injecting your own HttpClient 239 | ------------------------------ 240 | 241 | Because I am using the builder pattern, it is quite easy to inject your own HttpClient instance: 242 | 243 | HttpClient myHttpClient = ... 244 | 245 | RestClient.builder().httpClient(myHttpClient).build(); 246 | 247 | Set Cookies with your own HttpClient 248 | ------------------------------------- 249 | 250 | In order to set cookies, you need create your own HttpClient like so: 251 | 252 | BasicCookieStore cookieStore = new BasicCookieStore(); 253 | BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", "1234"); 254 | cookie.setDomain(".github.com"); 255 | cookie.setPath("/"); 256 | cookieStore.addCookie(cookie); 257 | 258 | //notice the useSystemPoperties() -> enables configuration (Proxy, SSL...) via system properties 259 | HttpClient myHttpClient = HttpClientBuilder.create() 260 | .setDefaultCookieStore(cookieStore) 261 | .useSystemProperties() 262 | .build(); 263 | 264 | RestClient client = RestClient.builder().httpClient(myHttpClient).build(); 265 | 266 | Injecting your own RestClient! 267 | -------------------------------- 268 | 269 | Yup, your read right, you can even inject your own RestClient implementation thru the builder. 270 | 271 | // you can override any of the RestClient methods you desire 272 | 273 | public class MyRestClient extends RestClient { 274 | protected MyRestClient(RestClientBuilder builder) { 275 | super(builder); 276 | } 277 | 278 | @Override 279 | protected HttpGet newHttpGet(String url) { 280 | ... 281 | } 282 | } 283 | 284 | RestClient client = RestClient.builder().restClientClass(MyRestClient.class).build(); 285 | 286 | Look at `test.integration.CustomRestClientTest` to a see working example. 287 | 288 | 289 | Next Steps 290 | ------------------------------ 291 | 292 | Add more tests & a plugin to get code coverage. 293 | 294 | Currently there are 9 tests, 5 integration, and 4 unit. 295 | 296 | 297 | Cheers! 298 | 299 | 300 | 301 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import bintray.Keys._ 2 | 3 | name := "http-rest-client" 4 | 5 | organization := "g00dnatur3" 6 | 7 | version := "1.0.21" 8 | 9 | bintrayPublishSettings 10 | 11 | repository in bintray := "java-utils" 12 | 13 | licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0.html")) 14 | 15 | bintrayOrganization in bintray := None 16 | 17 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 18 | 19 | libraryDependencies ++= Seq( 20 | "org.apache.httpcomponents" % "httpclient" % "4.3.4", 21 | "commons-io" % "commons-io" % "2.4", 22 | "org.slf4j" % "slf4j-api" % "1.7.7", 23 | "org.slf4j" % "slf4j-simple" % "1.7.7", 24 | "com.fasterxml.jackson.core" % "jackson-databind" % "2.4.1.2", 25 | "com.google.guava" % "guava" % "17.0" % "test", 26 | "com.novocode" % "junit-interface" % "0.9" % "test", 27 | "org.mockito" % "mockito-all" % "1.9.5" % "test" 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /project/bintray.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Resolver.url( 2 | "bintray-sbt-plugin-releases", 3 | url("http://dl.bintray.com/content/sbt/sbt-plugin-releases"))( 4 | Resolver.ivyStylePatterns) 5 | 6 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.0 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1") 2 | 3 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0") 4 | -------------------------------------------------------------------------------- /src/main/java/http/rest/AbstractRestClient.java: -------------------------------------------------------------------------------- 1 | package http.rest; 2 | 3 | import java.io.IOException; 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.URLEncoder; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.apache.commons.io.Charsets; 10 | import org.apache.commons.io.IOUtils; 11 | import org.apache.http.HttpResponse; 12 | import org.apache.http.client.ClientProtocolException; 13 | import org.apache.http.client.HttpClient; 14 | import org.apache.http.client.methods.HttpDelete; 15 | import org.apache.http.client.methods.HttpGet; 16 | import org.apache.http.client.methods.HttpPost; 17 | import org.apache.http.client.methods.HttpPut; 18 | import org.apache.http.client.methods.HttpRequestBase; 19 | import org.apache.http.client.methods.HttpUriRequest; 20 | import org.apache.http.entity.ContentType; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.fasterxml.jackson.core.type.TypeReference; 25 | import com.fasterxml.jackson.databind.JsonNode; 26 | import com.fasterxml.jackson.databind.ObjectMapper; 27 | 28 | public abstract class AbstractRestClient { 29 | 30 | protected final Logger logger = LoggerFactory.getLogger(getClass()); 31 | 32 | protected final HttpClient client; 33 | 34 | protected final ObjectMapper mapper; 35 | 36 | protected final RequestInterceptor interceptor; 37 | 38 | protected AbstractRestClient(RestClientBuilder builder) { 39 | this.client = builder.client; 40 | this.mapper = builder.mapper; 41 | this.interceptor = builder.interceptor; 42 | } 43 | 44 | public static RestClientBuilder builder() { 45 | return new RestClientBuilder(); 46 | }; 47 | 48 | protected T bindObject(String source, Class entityClass) throws IOException { 49 | return mapper.readValue(source, entityClass); 50 | } 51 | 52 | protected List bindJsonArray(String source, TypeReference> type) throws IOException { 53 | return mapper.readValue(source, type); 54 | } 55 | 56 | protected JsonNode toJson(Object object) throws IOException { 57 | if (object instanceof JsonNode) 58 | return (JsonNode) object; 59 | return mapper.valueToTree(object); 60 | } 61 | 62 | protected JsonNode toJsonArray(List data) throws IOException { 63 | return mapper.convertValue(data, JsonNode.class); 64 | } 65 | 66 | protected byte[] contentAsBytes(HttpResponse response) throws IOException { 67 | return IOUtils.toByteArray(response.getEntity().getContent()); 68 | } 69 | 70 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request) 71 | throws ClientProtocolException, IOException { 72 | 73 | if (interceptor != null) { 74 | interceptor.intercept(request); 75 | } else if (this.interceptor != null) { 76 | this.interceptor.intercept(request); 77 | } 78 | return client.execute(request); 79 | } 80 | 81 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request, int expectedStatus) 82 | throws RestClientException { 83 | 84 | String method = request.getMethod(); 85 | String path = request.getURI().toString(); 86 | logger.info("Send -> " + method + " " + path); 87 | HttpResponse response = null; 88 | try { 89 | response = execute(interceptor, request); 90 | } catch (Exception e) { 91 | throw new RestClientException(e, response); 92 | } 93 | int status = response.getStatusLine().getStatusCode(); 94 | if (status >= 200 && status < 400) { 95 | logger.info("[" + status + "] Successfully sent " + method + " " + path); 96 | } else { 97 | logger.error("[" + status + "] Failed to send " + method + " " + path); 98 | } 99 | if (expectedStatus != status) { 100 | StringBuilder sb = new StringBuilder("Status of " + status); 101 | sb.append(" not equal to expected value of ").append(expectedStatus); 102 | throw new RestClientException(sb.toString(), response); 103 | } 104 | return response; 105 | } 106 | 107 | protected int successStatus(String method) { 108 | if (method.equalsIgnoreCase("POST")) { 109 | return 201; 110 | } 111 | return 200; 112 | } 113 | 114 | protected String appendParams(String path, Map params) { 115 | if (params != null) { 116 | return path + queryString(params); 117 | } 118 | return path; 119 | } 120 | 121 | protected String queryString(Map params) { 122 | if (params == null || params.isEmpty()) 123 | return ""; 124 | StringBuilder sb = new StringBuilder("?"); 125 | int i = 0; 126 | for (Map.Entry entry : params.entrySet()) { 127 | String key = entry.getKey(); 128 | String value = entry.getValue(); 129 | if (i > 0) { 130 | sb.append("&"); 131 | } 132 | try { 133 | sb.append(key).append("=").append(URLEncoder.encode(value, Charsets.UTF_8.toString())); 134 | } catch (UnsupportedEncodingException e) { 135 | e.printStackTrace(); 136 | } 137 | i++; 138 | } 139 | return sb.toString(); 140 | } 141 | 142 | protected T contentTypeJson(T request) { 143 | request.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString()); 144 | return request; 145 | } 146 | 147 | protected HttpPost newHttpPost(String url) { 148 | return new HttpPost(url); 149 | } 150 | 151 | protected HttpGet newHttpGet(String url) { 152 | return new HttpGet(url); 153 | } 154 | 155 | protected HttpPut newHttpPut(String url) { 156 | return new HttpPut(url); 157 | } 158 | 159 | protected HttpDelete newHttpDelete(String url) { 160 | return new HttpDelete(url); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/http/rest/RequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package http.rest; 2 | 3 | import org.apache.http.client.methods.HttpRequestBase; 4 | 5 | public class RequestInterceptor { 6 | 7 | public void intercept(HttpRequestBase request) { 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/http/rest/RestClient.java: -------------------------------------------------------------------------------- 1 | package http.rest; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.io.Charsets; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.http.Header; 10 | import org.apache.http.HttpEntity; 11 | import org.apache.http.HttpResponse; 12 | import org.apache.http.client.methods.HttpDelete; 13 | import org.apache.http.client.methods.HttpGet; 14 | import org.apache.http.client.methods.HttpPost; 15 | import org.apache.http.client.methods.HttpPut; 16 | import org.apache.http.entity.StringEntity; 17 | import org.apache.http.util.EntityUtils; 18 | 19 | import com.fasterxml.jackson.core.type.TypeReference; 20 | 21 | public class RestClient extends AbstractRestClient { 22 | 23 | protected RestClient(RestClientBuilder builder) { 24 | super(builder); 25 | } 26 | 27 | protected String get(RequestInterceptor interceptor, String path, Map queryParams, 28 | int expectedStatus) throws RestClientException, IOException { 29 | 30 | HttpGet get = newHttpGet(appendParams(path, queryParams)); 31 | HttpResponse response = execute(interceptor, get, expectedStatus); 32 | String content = null; 33 | try { 34 | content = contentAsString(response); 35 | } catch (IOException e) { 36 | consume(response); 37 | } 38 | return content; 39 | } 40 | 41 | public T get(RequestInterceptor interceptor, String path, Map queryParams, 42 | Class entityClass, int expectedStatus) throws RestClientException, IOException { 43 | String content = get(interceptor, path, queryParams, expectedStatus); 44 | if (content != null) { 45 | return bindObject(content, entityClass); 46 | } else { 47 | return null; 48 | } 49 | } 50 | 51 | public List get(RequestInterceptor interceptor, String path, Map params, 52 | TypeReference> type, int expectedStatus) throws RestClientException, IOException { 53 | String content = get(interceptor, path, params, expectedStatus); 54 | if (content != null) { 55 | return bindJsonArray(content, type); 56 | } else { 57 | return null; 58 | } 59 | } 60 | 61 | public T get(RequestInterceptor interceptor, String path, Map params, Class entityClass) 62 | throws RestClientException, IOException { 63 | return get(interceptor, path, params, entityClass, 200); 64 | } 65 | 66 | public T get(String path, Map params, Class entityClass, int expectedStatus) 67 | throws RestClientException, IOException { 68 | return get(null, path, params, entityClass, expectedStatus); 69 | } 70 | 71 | public T get(String path, Map params, Class entityClass) throws RestClientException, 72 | IOException { 73 | return get(null, path, params, entityClass, 200); 74 | } 75 | 76 | public List get(RequestInterceptor interceptor, String path, Map params, 77 | TypeReference> type) throws RestClientException, IOException { 78 | return get(interceptor, path, params, type, 200); 79 | } 80 | 81 | public List get(String path, Map params, TypeReference> type, int expectedStatus) 82 | throws RestClientException, IOException { 83 | return get(null, path, params, type, expectedStatus); 84 | } 85 | 86 | public List get(String path, Map params, TypeReference> type) 87 | throws RestClientException, IOException { 88 | return get(null, path, params, type, 200); 89 | } 90 | 91 | public Header create(RequestInterceptor interceptor, String path, Object object, int expectedStatus) 92 | throws RestClientException, IOException { 93 | HttpPost post = contentTypeJson(newHttpPost(path)); 94 | HttpEntity entity = new StringEntity(toJson(object).toString(), Charsets.UTF_8); 95 | post.setEntity(entity); 96 | HttpResponse response = execute(interceptor, post, expectedStatus); 97 | consume(response); 98 | return response.getFirstHeader("Location"); 99 | } 100 | 101 | public Header create(String path, Object object, int expectedStatus) throws RestClientException, IOException { 102 | return create(null, path, object, expectedStatus); 103 | } 104 | 105 | public Header create(RequestInterceptor interceptor, String path, Object object) throws RestClientException, 106 | IOException { 107 | return create(interceptor, path, object, 201); 108 | } 109 | 110 | public Header create(String path, Object object) throws RestClientException, IOException { 111 | return create(null, path, object, 201); 112 | } 113 | 114 | public Header create(RequestInterceptor interceptor, String path, List data, int expectedStatus) 115 | throws RestClientException, IOException { 116 | HttpPost post = contentTypeJson(newHttpPost(path)); 117 | HttpEntity entity = new StringEntity(toJsonArray(data).toString(), Charsets.UTF_8); 118 | post.setEntity(entity); 119 | HttpResponse response = execute(interceptor, post, expectedStatus); 120 | consume(response); 121 | return response.getFirstHeader("Location"); 122 | } 123 | 124 | public Header create(String path, List data, int expectedStatus) throws RestClientException, IOException { 125 | return create(null, path, data, expectedStatus); 126 | } 127 | 128 | public Header create(RequestInterceptor interceptor, String path, List data) throws RestClientException, 129 | IOException { 130 | return create(interceptor, path, data, 201); 131 | } 132 | 133 | public Header create(String path, List data) throws RestClientException, IOException { 134 | return create(null, path, data, 201); 135 | } 136 | 137 | public void delete(RequestInterceptor interceptor, String path, int expectedStatus) throws RestClientException, 138 | IOException { 139 | HttpDelete delete = newHttpDelete(path); 140 | consume(execute(interceptor, delete, expectedStatus)); 141 | } 142 | 143 | public void delete(RequestInterceptor interceptor, String path) throws RestClientException, IOException { 144 | delete(interceptor, path, 200); 145 | } 146 | 147 | public void delete(String path, int expectedStatus) throws RestClientException, IOException { 148 | delete(null, path, expectedStatus); 149 | } 150 | 151 | public void delete(String path) throws RestClientException, IOException { 152 | delete(null, path, 200); 153 | } 154 | 155 | public void update(RequestInterceptor interceptor, String path, Object object, int expectedStatus) 156 | throws RestClientException, IOException { 157 | 158 | HttpPut put = contentTypeJson(newHttpPut(path)); 159 | HttpEntity entity = new StringEntity(toJson(object).toString(), Charsets.UTF_8); 160 | put.setEntity(entity); 161 | consume(execute(interceptor, put, expectedStatus)); 162 | } 163 | 164 | public void update(RequestInterceptor interceptor, String path, Object object) throws RestClientException, 165 | IOException { 166 | update(interceptor, path, object, 200); 167 | } 168 | 169 | public void update(String path, Object object) throws RestClientException, IOException { 170 | update(null, path, object, 200); 171 | } 172 | 173 | public void update(String path, Object object, int expectedStatus) throws RestClientException, IOException { 174 | update(null, path, object, expectedStatus); 175 | } 176 | 177 | public void update(RequestInterceptor interceptor, String path, List data, int expectedStatus) 178 | throws RestClientException, IOException { 179 | 180 | HttpPut put = contentTypeJson(newHttpPut(path)); 181 | HttpEntity entity = new StringEntity(toJsonArray(data).toString(), Charsets.UTF_8); 182 | put.setEntity(entity); 183 | consume(execute(interceptor, put, expectedStatus)); 184 | } 185 | 186 | public void update(RequestInterceptor interceptor, String path, List data) throws RestClientException, 187 | IOException { 188 | update(interceptor, path, data, 200); 189 | } 190 | 191 | public void update(String path, List data) throws RestClientException, IOException { 192 | update(null, path, data, 200); 193 | } 194 | 195 | public void update(String path, List data, int expectedStatus) throws RestClientException, IOException { 196 | update(null, path, data, expectedStatus); 197 | } 198 | 199 | public void consume(HttpResponse response) throws IOException { 200 | if (response != null && response.getEntity() != null) { 201 | EntityUtils.consume(response.getEntity()); 202 | } 203 | } 204 | 205 | public String contentAsString(HttpResponse response) throws IOException { 206 | if (response != null && response.getEntity() != null) { 207 | return IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); 208 | } 209 | return null; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/http/rest/RestClientBuilder.java: -------------------------------------------------------------------------------- 1 | package http.rest; 2 | 3 | import java.lang.reflect.Constructor; 4 | 5 | import org.apache.http.client.HttpClient; 6 | import org.apache.http.impl.client.HttpClientBuilder; 7 | 8 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | public class RestClientBuilder { 12 | 13 | protected HttpClient client; 14 | 15 | protected ObjectMapper mapper; 16 | 17 | protected RequestInterceptor interceptor; 18 | 19 | protected Class clazz; 20 | 21 | protected RestClientBuilder() { 22 | } 23 | 24 | public RestClientBuilder httpClient(HttpClient client) { 25 | this.client = client; 26 | return this; 27 | } 28 | 29 | public RestClientBuilder objectMapper(ObjectMapper mapper) { 30 | this.mapper = mapper; 31 | return this; 32 | } 33 | 34 | public RestClientBuilder requestInterceptor(RequestInterceptor interceptor) { 35 | this.interceptor = interceptor; 36 | return this; 37 | } 38 | 39 | public RestClientBuilder restClientClass(Class clazz) { 40 | this.clazz = clazz; 41 | return this; 42 | } 43 | 44 | public RestClient build() { 45 | if (clazz == null) { 46 | clazz = RestClient.class; 47 | } 48 | if (mapper == null) { 49 | mapper = new ObjectMapper(); 50 | mapper.setSerializationInclusion(Include.NON_NULL); 51 | } 52 | if (client == null) { 53 | client = HttpClientBuilder.create().useSystemProperties().build(); 54 | } 55 | return createRestClient(this, clazz); 56 | } 57 | 58 | protected T createRestClient(RestClientBuilder builder, Class restClientClass) { 59 | try { 60 | Constructor constructor = restClientClass.getDeclaredConstructor(RestClientBuilder.class); 61 | constructor.setAccessible(true); 62 | return constructor.newInstance(builder); 63 | } catch (Exception e) { 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/http/rest/RestClientException.java: -------------------------------------------------------------------------------- 1 | package http.rest; 2 | 3 | import org.apache.http.HttpResponse; 4 | 5 | public class RestClientException extends Exception { 6 | private static final long serialVersionUID = -785745983831601722L; 7 | 8 | private final HttpResponse response; 9 | 10 | public RestClientException(String message, HttpResponse response) { 11 | super(message); 12 | this.response = response; 13 | } 14 | 15 | public RestClientException(Exception e, HttpResponse response) { 16 | super(e); 17 | this.response = response; 18 | } 19 | 20 | public HttpResponse response() { 21 | return response; 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/test/Assertions.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | 7 | public class Assertions { 8 | 9 | public static void hasAddressComponents(JsonNode node) { 10 | assertNotNull(node); 11 | assertNotNull(node.get("results")); 12 | assertNotNull(node.get("results").get(0)); 13 | assertNotNull(node.get("results").get(0).get("address_components")); 14 | assertNotNull(node.get("results").get(0).get("address_components").get(0)); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/test/Settings.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.commons.io.IOUtils; 6 | 7 | public class Settings { 8 | 9 | public static final String geocoderUrl = "http://maps.googleapis.com/maps/api/geocode/json"; 10 | 11 | public static final String mockLocationHeader = "http://mock.org/created/1"; 12 | 13 | public static final String geocodeJson; 14 | 15 | static { 16 | geocodeJson = loadMockJson("/geocode.json"); 17 | } 18 | 19 | public static String loadMockJson(String path) { 20 | try { 21 | return IOUtils.toString(Settings.class.getResourceAsStream(path)); 22 | } catch (IOException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/test/integration/CustomRestClientTest.java: -------------------------------------------------------------------------------- 1 | package test.integration; 2 | 3 | import http.rest.RestClient; 4 | import http.rest.RestClientBuilder; 5 | 6 | import java.util.Map; 7 | 8 | import org.apache.http.client.methods.HttpGet; 9 | import org.junit.Test; 10 | 11 | import test.Assertions; 12 | import test.Settings; 13 | 14 | import com.fasterxml.jackson.databind.JsonNode; 15 | import com.google.common.collect.Maps; 16 | 17 | public class CustomRestClientTest { 18 | 19 | RestClient client; 20 | 21 | @Test 22 | public void injectMyRestClient() throws Exception { 23 | client = RestClient.builder().restClientClass(MyRestClient.class).build(); 24 | JsonNode node = client.get(null, null, JsonNode.class); 25 | Assertions.hasAddressComponents(node); 26 | } 27 | 28 | public static class MyRestClient extends RestClient { 29 | protected MyRestClient(RestClientBuilder builder) { 30 | super(builder); 31 | } 32 | 33 | @Override 34 | protected HttpGet newHttpGet(String url) { 35 | Map params = Maps.newHashMap(); 36 | params.put("address", "1980 W. Bayshore Rd. 94303"); 37 | params.put("sensor", "false"); 38 | url = appendParams(Settings.geocoderUrl, params); 39 | return new HttpGet(url); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/test/integration/GoogleGeocodeTest.java: -------------------------------------------------------------------------------- 1 | package test.integration; 2 | 3 | import http.rest.RestClient; 4 | 5 | import java.net.URLEncoder; 6 | import java.util.Map; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import test.Assertions; 12 | import test.Settings; 13 | 14 | import com.fasterxml.jackson.databind.JsonNode; 15 | import com.google.common.base.Charsets; 16 | import com.google.common.collect.Maps; 17 | 18 | public class GoogleGeocodeTest { 19 | 20 | RestClient client; 21 | 22 | @Before 23 | public void before() { 24 | client = RestClient.builder().build(); 25 | } 26 | 27 | @Test 28 | public void getGeocodeWithParamsAsMap() throws Exception { 29 | Map params = Maps.newHashMap(); 30 | params.put("address", "1980 W. Bayshore Rd. 94303"); 31 | params.put("sensor", "false"); 32 | doAssertions(client.get(Settings.geocoderUrl, params, JsonNode.class)); 33 | } 34 | 35 | @Test 36 | public void getGeocodeWithParamsAsString() throws Exception { 37 | String address = "1980 W. Bayshore Rd. 94303"; 38 | String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString()) 39 | + "&sensor=false"; 40 | doAssertions(client.get(url, null, JsonNode.class)); 41 | } 42 | 43 | private void doAssertions(JsonNode node) { 44 | Assertions.hasAddressComponents(node); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/test/integration/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | package test.integration; 2 | 3 | import http.rest.RequestInterceptor; 4 | import http.rest.RestClient; 5 | 6 | import java.net.URLEncoder; 7 | 8 | import org.apache.commons.io.Charsets; 9 | import org.apache.http.client.methods.HttpRequestBase; 10 | import org.apache.http.client.methods.HttpUriRequest; 11 | import org.apache.http.params.BasicHttpParams; 12 | import org.apache.http.params.HttpParams; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import test.Assertions; 17 | import test.Settings; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | 21 | public class RequestInterceptorTest { 22 | 23 | RestClient client; 24 | 25 | @Before 26 | public void before() { 27 | client = RestClient.builder().build(); 28 | } 29 | 30 | @Test 31 | public void interceptAllRequestsThruTheClientBuilder() throws Exception { 32 | final String address = "1980 W. Bayshore Rd. 94303"; 33 | final String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString()) 34 | + "&sensor=false"; 35 | 36 | client = RestClient.builder().requestInterceptor(new RequestInterceptor() { 37 | @Override 38 | public void intercept(HttpRequestBase request) { 39 | setParams(request, address, false); 40 | } 41 | }).build(); 42 | doAssertions(client.get(url, null, JsonNode.class)); 43 | } 44 | 45 | @Test 46 | public void interceptReqeustsAsNeeded() throws Exception { 47 | final String address = "1980 W. Bayshore Rd. 94303"; 48 | final String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString()) 49 | + "&sensor=false"; 50 | 51 | doAssertions(client.get(new RequestInterceptor() { 52 | @Override 53 | public void intercept(HttpRequestBase request) { 54 | setParams(request, address, false); 55 | } 56 | }, url, null, JsonNode.class)); 57 | } 58 | 59 | private void setParams(HttpUriRequest request, String address, boolean sensor) { 60 | // i don't recommend to set parameters this way, 61 | // its only here so i can verify the interceptor is working 62 | HttpParams params = new BasicHttpParams(); 63 | params.setParameter("address", address); 64 | params.setBooleanParameter("sensor", false); 65 | request.setParams(params); 66 | } 67 | 68 | private void doAssertions(JsonNode node) { 69 | Assertions.hasAddressComponents(node); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/test/unit/BaseTest.java: -------------------------------------------------------------------------------- 1 | package test.unit; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.when; 5 | import http.rest.RequestInterceptor; 6 | import http.rest.RestClient; 7 | import http.rest.RestClientBuilder; 8 | 9 | import java.io.IOException; 10 | 11 | import org.apache.commons.io.IOUtils; 12 | import org.apache.http.Header; 13 | import org.apache.http.HttpEntity; 14 | import org.apache.http.HttpResponse; 15 | import org.apache.http.StatusLine; 16 | import org.apache.http.client.ClientProtocolException; 17 | import org.apache.http.client.methods.HttpGet; 18 | import org.apache.http.client.methods.HttpPost; 19 | import org.apache.http.client.methods.HttpRequestBase; 20 | import org.apache.http.client.methods.HttpUriRequest; 21 | 22 | import test.Settings; 23 | 24 | public class BaseTest { 25 | 26 | RestClient client; 27 | 28 | public static class MockRestClient extends RestClient { 29 | 30 | protected MockRestClient(RestClientBuilder builder) { 31 | super(builder); 32 | } 33 | 34 | @Override 35 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request) 36 | throws ClientProtocolException, IOException { 37 | 38 | return mockHttpResponse(request); 39 | } 40 | } 41 | 42 | public static HttpResponse mockHttpResponse(HttpUriRequest request) throws IOException { 43 | HttpResponse mockHttpResponse = mock(HttpResponse.class); 44 | StatusLine statusLine = mock(StatusLine.class); 45 | if (request instanceof HttpPost) { 46 | when(statusLine.getStatusCode()).thenReturn(201); 47 | when(mockHttpResponse.getStatusLine()).thenReturn(statusLine); 48 | Header header = mock(Header.class); 49 | when(header.getValue()).thenReturn(Settings.mockLocationHeader); 50 | when(mockHttpResponse.getFirstHeader("Location")).thenReturn(header); 51 | } else { 52 | when(statusLine.getStatusCode()).thenReturn(200); 53 | when(mockHttpResponse.getStatusLine()).thenReturn(statusLine); 54 | } 55 | if (request instanceof HttpGet) { 56 | HttpEntity entity = mock(HttpEntity.class); 57 | when(entity.getContent()).thenReturn(IOUtils.toInputStream(Settings.geocodeJson)); 58 | when(mockHttpResponse.getEntity()).thenReturn(entity); 59 | } 60 | return mockHttpResponse; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/test/unit/TestRestClient.java: -------------------------------------------------------------------------------- 1 | package test.unit; 2 | 3 | import junit.framework.Assert; 4 | import http.rest.RestClient; 5 | 6 | import org.apache.http.Header; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import test.Assertions; 11 | import test.Settings; 12 | 13 | import com.fasterxml.jackson.databind.JsonNode; 14 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 15 | import com.fasterxml.jackson.databind.node.ObjectNode; 16 | 17 | public class TestRestClient extends BaseTest { 18 | 19 | RestClient client; 20 | 21 | @Before 22 | public void before() throws Exception { 23 | client = RestClient.builder().restClientClass(MockRestClient.class).build(); 24 | } 25 | 26 | @Test 27 | public void get() throws Exception { 28 | JsonNode node = client.get(Settings.geocoderUrl, null, JsonNode.class); 29 | Assertions.hasAddressComponents(node); 30 | } 31 | 32 | @Test 33 | public void post() throws Exception { 34 | Header header = client.create(Settings.geocoderUrl, new ObjectNode(JsonNodeFactory.instance)); 35 | Assert.assertEquals(Settings.mockLocationHeader, header.getValue()); 36 | } 37 | 38 | @Test 39 | public void put() throws Exception { 40 | client.update(Settings.geocoderUrl, new ObjectNode(JsonNodeFactory.instance)); 41 | } 42 | 43 | @Test 44 | public void delete() throws Exception { 45 | client.delete(Settings.geocoderUrl); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/resources/geocode.json: -------------------------------------------------------------------------------- 1 | {"results":[{"address_components":[{"long_name":"1980","short_name":"1980","types":["street_number"]},{"long_name":"West Bayshore Road","short_name":"W Bayshore Rd","types":["route"]},{"long_name":"East Palo Alto","short_name":"East Palo Alto","types":["locality","political"]},{"long_name":"California","short_name":"CA","types":["administrative_area_level_1","political"]},{"long_name":"United States","short_name":"US","types":["country","political"]},{"long_name":"94303","short_name":"94303","types":["postal_code"]}],"formatted_address":"1980 West Bayshore Road, East Palo Alto, CA 94303, USA","geometry":{"bounds":{"northeast":{"lat":37.4528755,"lng":-122.1285317},"southwest":{"lat":37.4528638,"lng":-122.1285426}},"location":{"lat":37.4528638,"lng":-122.1285426},"location_type":"RANGE_INTERPOLATED","viewport":{"northeast":{"lat":37.45421863029151,"lng":-122.1271881697085},"southwest":{"lat":37.45152066970851,"lng":-122.1298861302915}}},"types":["street_address"]}],"status":"OK"} 2 | --------------------------------------------------------------------------------