Skip to content

Commit

Permalink
Support jersey2 for client/server
Browse files Browse the repository at this point in the history
  • Loading branch information
mattnelson committed Jul 20, 2016
1 parent 5cf3c37 commit 3ebc9be
Show file tree
Hide file tree
Showing 37 changed files with 1,697 additions and 181 deletions.
3 changes: 2 additions & 1 deletion eureka-client-jersey2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ configurations.all {

dependencies {
compile project(':eureka-client')
compile "org.glassfish.jersey.core:jersey-client:2.8"
compile 'org.glassfish.jersey.core:jersey-client:2.23.1'
compile 'org.glassfish.jersey.connectors:jersey-apache-connector:2.23.1'
compile "javax.mail:mail:1.4.7"

testCompile project(':eureka-test-utils')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.netflix.discovery;

import javax.ws.rs.client.ClientRequestFilter;

/**
* Jersey2 implementation of DiscoveryClientOptionalArgs that supports supplying {@link ClientRequestFilter}
*/
public class Jersey2DiscoveryClientOptionalArgs extends AbstractDiscoveryClientOptionalArgs<ClientRequestFilter> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
/*
* Copyright 2015 Netflix, Inc.
*
* 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 com.netflix.discovery.shared.transport.jersey2;

import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;


import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.discovery.shared.transport.EurekaHttpResponse.EurekaHttpResponseBuilder;
import com.netflix.discovery.util.StringUtil;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse;

/**
* @author Tomasz Bak
*/
public abstract class AbstractJersey2EurekaHttpClient implements EurekaHttpClient {

private static final Logger logger = LoggerFactory.getLogger(AbstractJersey2EurekaHttpClient.class);

protected final EurekaJersey2Client jerseyClient;
protected final String serviceUrl;
private final boolean allowRedirect;
private final String userName;
private final String password;

protected AbstractJersey2EurekaHttpClient(EurekaJersey2Client jerseyClient, String serviceUrl, boolean allowRedirect) {
this.jerseyClient = jerseyClient;
this.serviceUrl = serviceUrl;
this.allowRedirect = allowRedirect;

// Jersey2 does not read credentials from the URI. We extract it here and enable authentication feature.
String localUserName = null;
String localPassword = null;
try {
URI serviceURI = new URI(serviceUrl);
if (serviceURI.getUserInfo() != null) {
String[] credentials = serviceURI.getUserInfo().split(":");
if (credentials.length == 2) {
localUserName = credentials[0];
localPassword = credentials[1];
}
}
} catch (URISyntaxException ignore) {
}
this.userName = localUserName;
this.password = localPassword;
}

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
Response response = null;
try {
Builder resourceBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request();
addExtraProperties(resourceBuilder);
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header(HttpHeaders.ACCEPT_ENCODING, "gzip")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(Entity.json(info));
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<Void> cancel(String appName, String id) {
String urlPath = "apps/" + appName + '/' + id;
Response response = null;
try {
Builder resourceBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request();
addExtraProperties(resourceBuilder);
addExtraHeaders(resourceBuilder);
response = resourceBuilder.delete();
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + '/' + id;
Response response = null;
try {
WebTarget webResource = jerseyClient.getClient().target(serviceUrl)
.path(urlPath)
.queryParam("status", info.getStatus().toString())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
if (overriddenStatus != null) {
webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
}
Builder requestBuilder = webResource.request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE)); // Jersey2 refuses to handle PUT with no body
EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
if (response.hasEntity()) {
eurekaResponseBuilder.entity(response.readEntity(InstanceInfo.class));
}
return eurekaResponseBuilder.build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<Void> statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info) {
String urlPath = "apps/" + appName + '/' + id + "/status";
Response response = null;
try {
Builder requestBuilder = jerseyClient.getClient().target(serviceUrl)
.path(urlPath)
.queryParam("value", newStatus.name())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString())
.request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.put(Entity.text(""));
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id, InstanceInfo info) {
String urlPath = "apps/" + appName + '/' + id + "/status";
Response response = null;
try {
Builder requestBuilder = jerseyClient.getClient().target(serviceUrl)
.path(urlPath)
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString())
.request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.delete();
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
return getApplicationsInternal("apps/", regions);
}

@Override
public EurekaHttpResponse<Applications> getDelta(String... regions) {
return getApplicationsInternal("apps/delta", regions);
}

@Override
public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
return getApplicationsInternal("vips/" + vipAddress, regions);
}

@Override
public EurekaHttpResponse<Applications> getSecureVip(String secureVipAddress, String... regions) {
return getApplicationsInternal("svips/" + secureVipAddress, regions);
}

@Override
public EurekaHttpResponse<Application> getApplication(String appName) {
String urlPath = "apps/" + appName;
Response response = null;
try {
Builder requestBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get();

Application application = null;
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
application = response.readEntity(Application.class);
}
return anEurekaHttpResponse(response.getStatus(), application).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
Response response = null;
String regionsParamValue = null;
try {
WebTarget webTarget = jerseyClient.getClient().target(serviceUrl).path(urlPath);
if (regions != null && regions.length > 0) {
webTarget = webTarget.queryParam("regions", StringUtil.join(regions));
}
Builder requestBuilder = webTarget.request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get();

Applications applications = null;
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
applications = response.readEntity(Applications.class);
}
return anEurekaHttpResponse(response.getStatus(), applications).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String id) {
return getInstanceInternal("instances/" + id);
}

@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id) {
return getInstanceInternal("apps/" + appName + '/' + id);
}

private EurekaHttpResponse<InstanceInfo> getInstanceInternal(String urlPath) {
Response response = null;
try {
Builder requestBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get();

InstanceInfo infoFromPeer = null;
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
infoFromPeer = response.readEntity(InstanceInfo.class);
}
return anEurekaHttpResponse(response.getStatus(), infoFromPeer).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

@Override
public void shutdown() {
jerseyClient.destroyResources();
}

protected void addExtraProperties(Builder webResource) {
if (userName != null) {
webResource.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, userName)
.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, password);
}
}

protected void addExtraHeaders(Builder webResource) {
if (allowRedirect) {
webResource.header(DiscoveryClient.HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true");
}
}

private static Map<String, String> headersOf(Response response) {
MultivaluedMap<String, String> jerseyHeaders = response.getStringHeaders();
if (jerseyHeaders == null || jerseyHeaders.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> headers = new HashMap<>();
for (Entry<String, List<String>> entry : jerseyHeaders.entrySet()) {
if (!entry.getValue().isEmpty()) {
headers.put(entry.getKey(), entry.getValue().get(0));
}
}
return headers;
}
}
Loading

0 comments on commit 3ebc9be

Please sign in to comment.