Marvin Froeder
7 years ago
committed by
GitHub
3 changed files with 279 additions and 0 deletions
@ -0,0 +1,134 @@
@@ -0,0 +1,134 @@
|
||||
/** |
||||
* Copyright 2012-2018 The Feign Authors |
||||
* |
||||
* 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. |
||||
*/ |
||||
package feign.jaxrs2; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.nio.charset.Charset; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.stream.Collectors; |
||||
import javax.ws.rs.client.ClientBuilder; |
||||
import javax.ws.rs.client.Entity; |
||||
import javax.ws.rs.core.*; |
||||
import feign.Client; |
||||
import feign.Request.Options; |
||||
|
||||
/** |
||||
* This module directs Feign's http requests to javax.ws.rs.client.Client . Ex: |
||||
* |
||||
* <pre> |
||||
* GitHub github = |
||||
* Feign.builder().client(new JaxRSClient()).target(GitHub.class, "https://api.github.com"); |
||||
* </pre> |
||||
*/ |
||||
public class JAXRSClient implements Client { |
||||
|
||||
private final ClientBuilder clientBuilder; |
||||
|
||||
public JAXRSClient() { |
||||
this(ClientBuilder.newBuilder()); |
||||
} |
||||
|
||||
public JAXRSClient(ClientBuilder clientBuilder) { |
||||
this.clientBuilder = clientBuilder; |
||||
} |
||||
|
||||
@Override |
||||
public feign.Response execute(feign.Request request, Options options) throws IOException { |
||||
final Response response = clientBuilder |
||||
.connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS) |
||||
.readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS) |
||||
.build() |
||||
.target(request.url()) |
||||
.request() |
||||
.headers(toMultivaluedMap(request.headers())) |
||||
.method(request.method(), createRequestEntity(request)); |
||||
|
||||
return feign.Response.builder() |
||||
.request(request) |
||||
.body(response.readEntity(InputStream.class), |
||||
integerHeader(response, HttpHeaders.CONTENT_LENGTH)) |
||||
.headers(toMap(response.getStringHeaders())) |
||||
.status(response.getStatus()) |
||||
.reason(response.getStatusInfo().getReasonPhrase()) |
||||
.build(); |
||||
} |
||||
|
||||
private Entity<byte[]> createRequestEntity(feign.Request request) { |
||||
if (request.body() == null) { |
||||
return null; |
||||
} |
||||
|
||||
return Entity.entity( |
||||
request.body(), |
||||
new Variant(mediaType(request.headers()), locale(request.headers()), |
||||
encoding(request.charset()))); |
||||
} |
||||
|
||||
private Integer integerHeader(Response response, String header) { |
||||
final MultivaluedMap<String, String> headers = response.getStringHeaders(); |
||||
if (!headers.containsKey(header)) { |
||||
return null; |
||||
} |
||||
|
||||
try { |
||||
return new Integer(headers.getFirst(header)); |
||||
} catch (final NumberFormatException e) { |
||||
// not a number or too big to fit Integer
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private String encoding(Charset charset) { |
||||
if (charset == null) |
||||
return null; |
||||
|
||||
return charset.name(); |
||||
} |
||||
|
||||
private String locale(Map<String, Collection<String>> headers) { |
||||
if (!headers.containsKey(HttpHeaders.CONTENT_LANGUAGE)) |
||||
return null; |
||||
|
||||
return headers.get(HttpHeaders.CONTENT_LANGUAGE).iterator().next(); |
||||
} |
||||
|
||||
private MediaType mediaType(Map<String, Collection<String>> headers) { |
||||
if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) |
||||
return null; |
||||
|
||||
return MediaType.valueOf(headers.get(HttpHeaders.CONTENT_TYPE).iterator().next()); |
||||
} |
||||
|
||||
private MultivaluedMap<String, Object> toMultivaluedMap(Map<String, Collection<String>> headers) { |
||||
final MultivaluedHashMap<String, Object> mvHeaders = new MultivaluedHashMap<>(); |
||||
|
||||
headers.entrySet().forEach(entry -> entry.getValue().stream() |
||||
.forEach(value -> mvHeaders.add(entry.getKey(), value))); |
||||
|
||||
return mvHeaders; |
||||
} |
||||
|
||||
private Map<String, Collection<String>> toMap(MultivaluedMap<String, String> headers) { |
||||
return headers.entrySet().stream() |
||||
.collect(Collectors.toMap( |
||||
Entry::getKey, |
||||
Entry::getValue)); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
/** |
||||
* Copyright 2012-2018 The Feign Authors |
||||
* |
||||
* 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. |
||||
*/ |
||||
package feign.jaxrs2; |
||||
|
||||
import feign.Feign.Builder; |
||||
import feign.Headers; |
||||
import feign.RequestLine; |
||||
import feign.Response; |
||||
import feign.Util; |
||||
import feign.assertj.MockWebServerAssertions; |
||||
import feign.client.AbstractClientTest; |
||||
import feign.jaxrs2.JAXRSClient; |
||||
import feign.Feign; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import org.junit.Test; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import javax.ws.rs.ProcessingException; |
||||
import static feign.Util.UTF_8; |
||||
import static java.util.Arrays.asList; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.junit.Assert.assertEquals; |
||||
import org.junit.Assume; |
||||
|
||||
/** Tests client-specific behavior, such as ensuring Content-Length is sent when specified. */ |
||||
public class JAXRSClientTest extends AbstractClientTest { |
||||
|
||||
@Override |
||||
public Builder newBuilder() { |
||||
return Feign.builder().client(new JAXRSClient()); |
||||
} |
||||
|
||||
@Override |
||||
public void testPatch() throws Exception { |
||||
try { |
||||
super.testPatch(); |
||||
} catch (final ProcessingException e) { |
||||
Assume.assumeNoException("JaxRS client do not support PATCH requests", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void noResponseBodyForPut() { |
||||
try { |
||||
super.noResponseBodyForPut(); |
||||
} catch (final IllegalStateException e) { |
||||
Assume.assumeNoException("JaxRS client do not support empty bodies on PUT", e); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void reasonPhraseIsOptional() throws IOException, InterruptedException { |
||||
server.enqueue(new MockResponse().setStatus("HTTP/1.1 " + 200)); |
||||
|
||||
final TestInterface api = newBuilder() |
||||
.target(TestInterface.class, "http://localhost:" + server.getPort()); |
||||
|
||||
final Response response = api.post("foo"); |
||||
|
||||
assertThat(response.status()).isEqualTo(200); |
||||
// jaxrsclient is creating a reason when none is present
|
||||
// assertThat(response.reason()).isNullOrEmpty();
|
||||
} |
||||
|
||||
@Test |
||||
public void parsesRequestAndResponse() throws IOException, InterruptedException { |
||||
server.enqueue(new MockResponse().setBody("foo").addHeader("Foo: Bar")); |
||||
|
||||
final TestInterface api = newBuilder() |
||||
.target(TestInterface.class, "http://localhost:" + server.getPort()); |
||||
|
||||
final Response response = api.post("foo"); |
||||
|
||||
assertThat(response.status()).isEqualTo(200); |
||||
assertThat(response.reason()).isEqualTo("OK"); |
||||
assertThat(response.headers()) |
||||
.containsEntry("Content-Length", asList("3")) |
||||
.containsEntry("Foo", asList("Bar")); |
||||
assertThat(response.body().asInputStream()) |
||||
.hasContentEqualTo(new ByteArrayInputStream("foo".getBytes(UTF_8))); |
||||
|
||||
MockWebServerAssertions.assertThat(server.takeRequest()).hasMethod("POST") |
||||
.hasPath("/?foo=bar&foo=baz&qux=") |
||||
.hasBody("foo"); |
||||
} |
||||
|
||||
@Test |
||||
public void testContentTypeWithoutCharset2() throws Exception { |
||||
server.enqueue(new MockResponse() |
||||
.setBody("AAAAAAAA")); |
||||
final JaxRSClientTestInterface api = newBuilder() |
||||
.target(JaxRSClientTestInterface.class, "http://localhost:" + server.getPort()); |
||||
|
||||
final Response response = api.getWithContentType(); |
||||
// Response length should not be null
|
||||
assertEquals("AAAAAAAA", Util.toString(response.body().asReader())); |
||||
|
||||
MockWebServerAssertions.assertThat(server.takeRequest()) |
||||
.hasHeaders("Accept: text/plain", "Content-Type: text/plain") // Note: OkHttp adds content
|
||||
// length.
|
||||
.hasMethod("GET"); |
||||
} |
||||
|
||||
|
||||
public interface JaxRSClientTestInterface { |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers({"Accept: text/plain", "Content-Type: text/plain"}) |
||||
Response getWithContentType(); |
||||
} |
||||
} |
Loading…
Reference in new issue