/*
* Copyright (c) 2014 Globo.com - ATeam
* All rights reserved.
*
* This source is subject to the Apache License, Version 2.0.
* Please see the LICENSE file for more information.
*
* Authors: See AUTHORS file
*
* 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.globo.galeb.server;
import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;
import com.globo.galeb.entity.IJsonable;
import com.globo.galeb.exceptions.AbstractHttpException;
import com.globo.galeb.logger.SafeLogger;
import com.globo.galeb.logger.impl.NcsaLogExtendedFormatter;
import com.globo.galeb.metrics.ICounter;
import com.globo.galeb.request.RouterRequest;
import com.globo.galeb.rulereturn.HttpCode;
import org.vertx.java.core.Handler;
import org.vertx.java.core.MultiMap;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.core.http.HttpServerResponse;
/**
* Class ServerResponse.
*
* @author: See AUTHORS file.
* @version: 1.0.0, Oct 23, 2014.
*/
public class ServerResponse {
/** The httpServerRequest. */
private final HttpServerRequest req;
/** The logger. */
private SafeLogger log = null;
/** The counter. */
private ICounter counter = null;
/** is enabled access log? */
private boolean enableAccessLog = false;
/** The httpServerResponse. */
private final HttpServerResponse resp;
/** The message. */
private String message = "";
/** The id. */
private String id = "";
/** The backend id. */
private String backendId = "";
/**
* Define logger if necessary.
*/
private void defineLoggerIfNecessary() {
if (log==null) {
log = new SafeLogger();
}
}
/**
* Convert Exception to HttpCode.
*
* @param e the expection
* @return the http code status
*/
private int exceptionToHttpCode(final Throwable e) {
if (e instanceof AbstractHttpException) {
return ((AbstractHttpException)e).getHttpCode();
} else {
return HttpCode.SERVICE_UNAVAILABLE;
}
}
/**
* Instantiates a new server response.
*
* @param req the req
*/
public ServerResponse(final HttpServerRequest req) {
this.req = req;
this.resp = req.response();
resp.exceptionHandler(new Handler<Throwable>() {
@Override
public void handle(Throwable event) {
showErrorAndClose(event);
}
});
}
/**
* Sets the id.
*
* @param id the id
* @return this
*/
public ServerResponse setId(String id) {
this.id = id;
return this;
}
/**
* Sets the backend id.
*
* @param backendId the backend id
* @return this
*/
public ServerResponse setBackendId(String backendId) {
this.backendId = backendId;
return this;
}
/**
* Sets the message.
*
* @param message the message
* @return this
*/
public ServerResponse setMessage(final String message) {
this.message = message;
return this;
}
/**
* Sets the status code.
*
* @param code the code
* @return this
*/
public ServerResponse setStatusCode(Integer code) {
resp.setStatusCode(code);
resp.setStatusMessage(HttpCode.getMessage(code));
return this;
}
/**
* Sets the headers.
*
* @param headers the headers
* @return this
*/
public ServerResponse setHeaders(final MultiMap headers) {
resp.headers().set(headers);
return this;
}
/**
* Show error and finish connection (and close, if necessary).
*
* @param event the event/exception
*/
public void showErrorAndClose(final Throwable event) {
int statusCode = exceptionToHttpCode(event);
setStatusCode(statusCode);
String headerHost = getHeaderHost();
String logMessage = String.format("FAIL with HttpStatus %d%s: %s",
statusCode,
!"".equals(headerHost) ? String.format(" (virtualhost: %s)", headerHost) : "",
HttpCode.getMessage(statusCode, false));
defineLoggerIfNecessary();
if (statusCode>=HttpCode.INTERNAL_SERVER_ERROR) {
log.error(logMessage);
log.debug(getStackTrace(event));
} else {
log.warn(logMessage);
}
endResponse();
try {
closeResponse();
} catch (IllegalStateException e) {
// Response has already been finish?
log.debug(e.getMessage());
} catch (RuntimeException e2) {
log.error(String.format("FAIL: statusCode %d, Error > %s", resp.getStatusCode(), e2.getMessage()));
}
}
/**
* Close response.
*/
public void closeResponse() throws RuntimeException {
resp.close();
}
/**
* Real end method.
*
* @param message the message
*/
private void realEnd() throws RuntimeException {
if (!"".equals(message)) {
resp.end(message);
} else {
resp.end();
}
}
/**
* Finish the connection.
*/
public void endResponse() {
logRequest();
sendRequestCount();
try {
realEnd();
} catch (RuntimeException e) {
defineLoggerIfNecessary();
log.debug(e);
}
}
/**
* Log the request.
*/
public void logRequest() {
if (enableAccessLog) {
Integer code = resp.getStatusCode();
String httpLogMessage = new NcsaLogExtendedFormatter()
.setRequestData(req)
.getFormatedLog();
defineLoggerIfNecessary();
if (HttpCode.isServerError(code.intValue())) {
log.error(httpLogMessage);
} else {
log.info(httpLogMessage);
}
}
}
/**
* Gets the header host.
*
* @return the header host
*/
private String getHeaderHost() {
if (req!=null) {
MultiMap headers = req.headers();
return headers.contains(RouterRequest.HTTP_HEADER_HOST) ? headers.get(RouterRequest.HTTP_HEADER_HOST): "";
}
return "";
}
/**
* Send request count to counter.
*/
public void sendRequestCount() {
int code = HttpCode.INTERNAL_SERVER_ERROR;
if (req!=null) {
code = resp.getStatusCode();
}
String headerHost = getHeaderHost();
if (counter!=null) {
if (!"".equals(headerHost) && !IJsonable.UNDEF.equals(headerHost) &&
!"".equals(backendId) && !IJsonable.UNDEF.equals(backendId)) {
counter.httpCode(headerHost, backendId, code);
} else if (!"".equals(id) && !IJsonable.UNDEF.equals(id)) {
counter.httpCode(id, code);
}
}
}
/**
* Sets the chunked.
*
* @param enableChunked the enable chunked
* @return the server response
*/
public ServerResponse setChunked(Boolean enableChunked) {
this.resp.setChunked(enableChunked);
return this;
}
/**
* Sets the counter.
*
* @param counter the counter
* @return the server response
*/
public ServerResponse setCounter(final ICounter counter) {
this.counter = counter;
return this;
}
/**
* Sets the enable access log.
*
* @param enableAccessLog the enable access log
* @return the server response
*/
public ServerResponse setEnableAccessLog(boolean enableAccessLog) {
this.enableAccessLog = enableAccessLog;
return this;
}
/**
* Sets the log.
*
* @param log the log
* @return the server response
*/
public ServerResponse setLog(final SafeLogger alog) {
this.log = alog;
return this;
}
}