/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.giraph.debugger.gui;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.apache.log4j.Logger;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
/**
* The Abstract class for HTTP handlers.
*/
public abstract class ServerHttpHandler implements HttpHandler {
/**
* Logger for this class.
*/
private static final Logger LOG = Logger.getLogger(ServerHttpHandler.class);
/**
* Response body.
*/
protected String response;
/**
* Response body as a byte array
*/
protected byte[] responseBytes;
/**
* Response status code. Please use HttpUrlConnection final static members.
*/
protected int statusCode;
/**
* MimeType of the response. Please use MediaType final static members.
*/
protected String responseContentType;
/**
* HttpExchange object received in the handle call.
*/
protected HttpExchange httpExchange;
/**
* Handles an HTTP call's lifecycle - read parameters, process and send
* response.
* @param httpExchange the http exchange object.
*/
@Override
public void handle(HttpExchange httpExchange) throws IOException {
// Assign class members so that subsequent methods can use it.
this.httpExchange = httpExchange;
// Set application/json as the default content type.
this.responseContentType = MediaType.APPLICATION_JSON;
String rawUrl = httpExchange.getRequestURI().getQuery();
Map<String, String> paramMap;
try {
paramMap = ServerUtils.getUrlParams(rawUrl);
// Call the method implemented by inherited classes.
LOG.info(httpExchange.getRequestURI().getPath() + paramMap.toString());
processRequest(httpExchange, paramMap);
} catch (UnsupportedEncodingException ex) {
this.statusCode = HttpURLConnection.HTTP_BAD_REQUEST;
this.response = "Malformed URL. Given encoding is not supported.";
}
// In case of an error statusCode, we just write the exception string.
// (Consider using JSON).
if (this.statusCode != HttpURLConnection.HTTP_OK) {
this.responseContentType = MediaType.TEXT_PLAIN;
}
// Set mandatory Response Headers.
this.setMandatoryResponseHeaders();
this.writeResponse();
}
/**
* Writes the text response.
*/
private void writeResponse() throws IOException {
OutputStream os = this.httpExchange.getResponseBody();
if (this.responseContentType == MediaType.APPLICATION_JSON ||
this.responseContentType == MediaType.TEXT_PLAIN) {
this.httpExchange.sendResponseHeaders(this.statusCode,
this.response.length());
os.write(this.response.getBytes());
} else if (this.responseContentType == MediaType.APPLICATION_OCTET_STREAM) {
this.httpExchange.sendResponseHeaders(this.statusCode,
this.responseBytes.length);
os.write(this.responseBytes);
}
os.close();
}
/**
* Add mandatory headers to the HTTP response by the debugger server. MUST be
* called before sendResponseHeaders.
*/
private void setMandatoryResponseHeaders() {
// TODO(vikesh): **REMOVE CORS FOR ALL AFTER DECIDING THE DEPLOYMENT
// ENVIRONMENT**
Headers headers = this.httpExchange.getResponseHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Content-Type", this.responseContentType);
}
/**
* Sets the given headerKey to the given headerValue.
*
* @param headerKey - Header Key
* @param headerValue - Header Value.
* @desc - For example, call like this to set the Content-disposition header
* setResponseHeader("Content-disposition", "attachment");
*/
protected void setResponseHeader(String headerKey, String headerValue) {
Headers responseHeaders = this.httpExchange.getResponseHeaders();
responseHeaders.add(headerKey, headerValue);
}
/**
* Handle the common exceptions in processRequest.
*
* @param e thrown exception.
* @param illegalArgumentMessage - Message when illegal argument
* exception is thrown. Optional - May be null.
*/
protected void handleException(Exception e, String illegalArgumentMessage) {
e.printStackTrace();
LOG.error(e);
if (e instanceof NumberFormatException) {
this.statusCode = HttpURLConnection.HTTP_BAD_REQUEST;
this.response = String.format("%s must be an integer >= -1.",
ServerUtils.SUPERSTEP_ID_KEY);
} else if (e instanceof IllegalArgumentException) {
this.statusCode = HttpURLConnection.HTTP_BAD_REQUEST;
this.response = illegalArgumentMessage;
} else if (e instanceof FileNotFoundException) {
this.statusCode = HttpURLConnection.HTTP_NOT_FOUND;
this.response = "File not found on the server. Please ensure this " +
"vertex/master was debugged.";
} else if (e instanceof IOException ||
e instanceof InstantiationException ||
e instanceof IllegalAccessException ||
e instanceof ClassNotFoundException) {
this.statusCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
this.response = "Internal Server Error.";
} else {
LOG.error("Unknown Exception: " + e.toString());
this.statusCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
this.response = "Unknown exception occured.";
}
}
/**
* Implement this method in inherited classes. This method MUST set statusCode
* and response (or responseBytes) class members appropriately. In case the
* Content type is not JSON, must specify the new Content type. Default type
* is application/json. Non-200 Status is automatically assigned text/plain.
*
* @param httpExchange the http exchange object within which the paramters
* will be set.
* @param paramMap map of parameters.
*/
public abstract void processRequest(HttpExchange httpExchange,
Map<String, String> paramMap);
}