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

Add API for retrieving application's current status #5

Merged
merged 2 commits into from
Nov 18, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
Add status handler and introduce execution context
  • Loading branch information
danveloper committed Nov 17, 2014
commit 67fdf505357a67a6ad7f8a50aa34162f8d413ffa
65 changes: 65 additions & 0 deletions src/main/java/com/netflix/prana/http/Context.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.netflix.prana.http;

import io.netty.handler.codec.http.HttpResponseStatus;

import java.util.List;

/**
* A context is a collection of functions to aid in the request handling process
*/
public interface Context {

/**
* Serializes an object and writes the bytes to the response stream
*
* @param object response object
*/
void send(Object object);

/**
* Sends a simple text message back through the response stream
*
* @param message the message to deliver
*/
void sendSimple(String message);

/**
* Serializes a simple error message back to the response using the supplied status code
*
* @param status the status code for the response
* @param message the message to send back to the caller
*/
void sendError(HttpResponseStatus status, String message);

/**
* Retrieves the value of the specified header
*
* @param name the name of the header to retrieve
* @return the header value
*/
String getHeader(String name);

/**
* Sets the response header value
*
* @param name the header's name
* @param value the value of the header
*/
void setHeader(String name, String value);

/**
* Convenience method for retrieving a query parameter off of the request
*
* @param key the query parameter
* @return the query param value
*/
String getQueryParam(String key);

/**
* Convenience method for retrieving a query parameter off of the request that contains multiple values
*
* @param key the name of the query parameter
* @return the list of values from the query parameter
*/
List<String> getQueryParams(String key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.netflix.prana.http.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.prana.http.Context;
import com.netflix.prana.internal.DefaultContext;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;

public abstract class AbstractRequestHandler implements RequestHandler<ByteBuf, ByteBuf> {

private final ObjectMapper objectMapper;

protected AbstractRequestHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

abstract void handle(Context context);

@Override
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, final HttpServerResponse<ByteBuf> response) {
DefaultContext context = new DefaultContext(request, response, objectMapper);
handle(context);
return context.getResponseSubject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,56 +15,35 @@
*/
package com.netflix.prana.http.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.config.DynamicProperty;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;
import com.netflix.prana.http.Context;

import java.util.ArrayList;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Created by dchoudhury on 10/20/14.
*/
public class DynamicPropertiesHandler implements RequestHandler<ByteBuf, ByteBuf> {
public class DynamicPropertiesHandler extends AbstractRequestHandler {

private final ObjectMapper objectMapper;
private static final String ID_QUERY_PARAMETER = "id";

public DynamicPropertiesHandler() {
this.objectMapper = new ObjectMapper();
@Inject
public DynamicPropertiesHandler(ObjectMapper objectMapper) {
super(objectMapper);
}

@Override
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
response.getHeaders().add("Content-Type", "application/json");
void handle(Context context) {
Map<String, String> properties = new HashMap<>();
List<String> ids = forQueryParam(request.getQueryParameters(), "id");
List<String> ids = context.getQueryParams(ID_QUERY_PARAMETER);
for (String id : ids) {
String property = DynamicProperty.getInstance(id).getString(null);
properties.put(id, property);
}
try {
response.writeBytes(objectMapper.writeValueAsBytes(properties));
return response.close();
} catch (JsonProcessingException e) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
return response.close();
}
context.send(properties);
}

private List<String> forQueryParam(Map<String, List<String>> queryParams, String paramName) {
List<String> values = queryParams.get(paramName);
if (values == null) {
return new ArrayList<>(1);
}
return values;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class HandlersModule extends AbstractModule {

@Override
protected void configure() {
bind(StatusHandler.class).in(Scopes.SINGLETON);
bind(SimpleRouter.class).in(Scopes.SINGLETON);
bind(ProxyHandler.class).in(Scopes.SINGLETON);
bind(HealthCheckHandler.class).in(Scopes.SINGLETON);
Expand Down
73 changes: 40 additions & 33 deletions src/main/java/com/netflix/prana/http/api/HealthCheckHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.netflix.prana.http.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.netflix.config.DynamicProperty;
import com.netflix.prana.http.Context;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelOption;
import io.netty.handler.codec.http.HttpResponseStatus;
Expand All @@ -25,51 +27,56 @@
import io.reactivex.netty.protocol.http.client.HttpClient;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;
import rx.exceptions.OnErrorThrowable;
import rx.functions.Func1;

import javax.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;

public class HealthCheckHandler implements RequestHandler<ByteBuf, ByteBuf> {
public class HealthCheckHandler extends AbstractRequestHandler {
private static final String DEFAULT_CONTENT_TYPE = "application/xml";
private static final Observable<Void> DEFAULT_NOOP_RESPONSE = Observable.just(null);

private final int DEFAULT_APPLICATION_PORT = 7101;
public static final int DEFAULT_APPLICATION_PORT = 7101;
public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
public static final String DEFAULT_HEALTHCHECK_ENDPOINT = "http://localhost:7001/healthcheck";
public static final String DEFAULT_OK_HEALTH = "<health>ok</health>";
public static final String DEFAULT_FAIL_HEALTH = "<health>fail</health>";

private final int DEFAULT_CONNECTION_TIMEOUT = 2000;

public Observable<Void> handle(HttpServerRequest<ByteBuf> serverRequest, final HttpServerResponse<ByteBuf> serverResponse) {
String externalHealthCheckURL = DynamicProperty.getInstance("prana.host.healthcheck.url").getString("http://localhost:7001/healthcheck");
serverResponse.getHeaders().add("Content-Type", "application/xml");
if (Strings.isNullOrEmpty(externalHealthCheckURL)) {
serverResponse.setStatus(HttpResponseStatus.OK);
serverResponse.writeBytes("<health>ok</health>".getBytes());
return serverResponse.close();
}
@Inject
public HealthCheckHandler(ObjectMapper objectMapper) {
super(objectMapper);
}

return getResponse(externalHealthCheckURL).flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<Void>>() {
@Override
public Observable<Void> call(HttpClientResponse<ByteBuf> response) {
if (response.getStatus().code() == HttpResponseStatus.OK.code()) {
serverResponse.setStatus(HttpResponseStatus.OK);
serverResponse.writeBytes("<health>ok</health>".getBytes());
return serverResponse.close();
@Override
void handle(final Context context) {
String externalHealthCheckURL = DynamicProperty.getInstance("prana.host.healthcheck.url")
.getString(DEFAULT_HEALTHCHECK_ENDPOINT);
context.setHeader("Content-type", DEFAULT_CONTENT_TYPE);
if (Strings.isNullOrEmpty(externalHealthCheckURL)) {
context.sendSimple(DEFAULT_OK_HEALTH);
} else {
getResponse(externalHealthCheckURL).flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<Void>>() {
@Override
public Observable<Void> call(HttpClientResponse<ByteBuf> response) {
if (response.getStatus().code() == HttpResponseStatus.OK.code()) {
context.sendSimple(DEFAULT_OK_HEALTH);
} else {
context.sendError(HttpResponseStatus.SERVICE_UNAVAILABLE, DEFAULT_FAIL_HEALTH);
}
return DEFAULT_NOOP_RESPONSE;
}
}).onErrorFlatMap(new Func1<OnErrorThrowable, Observable<Void>>() {
@Override
public Observable<Void> call(OnErrorThrowable onErrorThrowable) {
context.sendError(HttpResponseStatus.SERVICE_UNAVAILABLE, DEFAULT_FAIL_HEALTH);
return DEFAULT_NOOP_RESPONSE;
}
serverResponse.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
serverResponse.writeBytes("<health>fail</health>".getBytes());
return serverResponse.close();
}
}).onErrorFlatMap(new Func1<OnErrorThrowable, Observable<Void>>() {
@Override
public Observable<Void> call(OnErrorThrowable onErrorThrowable) {
serverResponse.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
serverResponse.writeBytes("<health>fail</health>".getBytes());
return serverResponse.close();
}
});
}).subscribe();
}
}

private Observable<HttpClientResponse<ByteBuf>> getResponse(String externalHealthCheckURL) {
Expand Down
54 changes: 17 additions & 37 deletions src/main/java/com/netflix/prana/http/api/HostsHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,48 @@
*/
package com.netflix.prana.http.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.prana.http.Context;
import com.netflix.prana.service.HostService;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;

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

import static com.netflix.appinfo.InstanceInfo.InstanceStatus;

/**
* Created by dchoudhury on 10/20/14.
*/
public class HostsHandler implements RequestHandler<ByteBuf, ByteBuf> {

private final ObjectMapper objectMapper;
public class HostsHandler extends AbstractRequestHandler {

private final HostService hostService;

@Inject
public HostsHandler(HostService hostService) {
public HostsHandler(HostService hostService, ObjectMapper objectMapper) {
super(objectMapper);
this.hostService = hostService;
this.objectMapper = new ObjectMapper();
}

@Override
public Observable<Void> handle(HttpServerRequest<ByteBuf> serverRequest, HttpServerResponse<ByteBuf> serverResponse) {
Map<String, List<String>> queryParameters = serverRequest.getQueryParameters();
String appName = Utils.forQueryParam(queryParameters, "appName");
String vip = Utils.forQueryParam(queryParameters, "vip");
public void handle(Context context) {
String appName = context.getQueryParam("appName");
String vip = context.getQueryParam("vip");
if (Strings.isNullOrEmpty(appName)) {
serverResponse.setStatus(HttpResponseStatus.BAD_REQUEST);
serverResponse.writeString("appName has to be specified");
return serverResponse.close();
}
List<InstanceInfo> instances = hostService.getHosts(appName);
List<String> hosts = new ArrayList<>();
for (InstanceInfo instanceInfo : instances) {
if (vip != null && !instanceInfo.getVIPAddress().contains(vip) && instanceInfo.getStatus().equals(InstanceStatus.UP)) {
continue;
context.sendError(HttpResponseStatus.BAD_REQUEST, "appName has to be specified");
} else {
List<InstanceInfo> instances = hostService.getHosts(appName);
List<String> hosts = new ArrayList<>();
for (InstanceInfo instanceInfo : instances) {
if (vip != null && !instanceInfo.getVIPAddress().contains(vip) && instanceInfo.getStatus().equals(InstanceStatus.UP)) {
continue;
}
hosts.add(instanceInfo.getHostName());
}
hosts.add(instanceInfo.getHostName());
}

try {
byte[] bytes = objectMapper.writeValueAsBytes(hosts);
serverResponse.getHeaders().set("Content-Type", "application/json");
serverResponse.writeBytes(bytes);
return serverResponse.close();
} catch (JsonProcessingException e) {
serverResponse.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
serverResponse.writeString(e.getMessage());
return serverResponse.close();
context.send(hosts);
}
}
}
27 changes: 17 additions & 10 deletions src/main/java/com/netflix/prana/http/api/PingHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@
*/
package com.netflix.prana.http.api;

import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.prana.http.Context;

public class PingHandler implements RequestHandler<ByteBuf, ByteBuf> {
import javax.inject.Inject;

public class PingHandler extends AbstractRequestHandler {

private static final String CACHE_CONTROL_HEADER = "Cache-Control";
private static final String CACHE_CONTROL_HEADER_VAL = "must-revalidate,no-cache,no-store";
private static final String DEFAULT_PONG_RESPONSE = "pong";

@Inject
public PingHandler(ObjectMapper objectMapper) {
super(objectMapper);
}

@Override
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
response.getHeaders().set("Cache-Control", "must-revalidate,no-cache,no-store");
response.writeString("pong");
return response.close();
void handle(Context context) {
context.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_HEADER_VAL);
context.sendSimple(DEFAULT_PONG_RESPONSE);
}
}
Loading