package com.yahoo.dtf.actions.http.server; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.entity.InputStreamEntity; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpRequestHandler; import com.yahoo.dtf.actions.Action; import com.yahoo.dtf.actions.http.Entity; import com.yahoo.dtf.actions.http.HttpBase; import com.yahoo.dtf.exception.DTFException; import com.yahoo.dtf.exception.ParseException; import com.yahoo.dtf.recorder.Event; import com.yahoo.dtf.state.ActionState; import com.yahoo.dtf.state.DTFState; import com.yahoo.dtf.streaming.DTFInputStream; import com.yahoo.dtf.util.HashUtil; import com.yahoo.dtf.util.streams.Throttler; /** * @dtf.feature HTTP Server * @dtf.feature.group HTTP * * @dtf.feature.desc * <p> * Within DTF it is possible to start your own HTTP server that will respond * to any type of HTTP method and be able to construct an HTTP response to your * required needs. You'll find that you have to be a bit careful when starting * and shutting down the httpserver because otherwise you may create a test that * gets stuck waiting for a parallel executed http_server that doesn't end * because you haven't issued the shutdown correctly. A simple recipe to follow * when using the http_server tag can be seen in the following lines: * </p> * * {@dtf.xml * <parallel> * <http_server port="28080"> * <http_listener path="/testpath" method="PUT"/> * </http_server> * * <try> * <log>Do something...</log> * <finally> * <http_server command="stop" port="28080"/> * </finally> * </try> * </parallel>} * * <p> * The previous template works well because after staring the HTTP server we're * very careful to protect the stop command within a try/finally statement * that guarantees we'll always stop the server before proceeding from whatever * client activity we have tried to execute. * </p> */ public class DTFHttpHandler implements HttpRequestHandler { private Http_listener _listener; private String _method; private DTFState _state; public DTFHttpHandler(String method, Http_listener listener) { super(); _listener = listener; _method = method.toUpperCase(); // duplicate the state so that multiple threads won't run into any // issues _state = Action.getState().duplicate(); } public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { String method = request.getRequestLine().getMethod().toUpperCase(); String target = request.getRequestLine().getUri(); StringBuffer headerlist = new StringBuffer(); Event event = new Event("http." + method.toLowerCase()); event.start(); event.stop(); Header[] headers = request.getAllHeaders(); for (int i = 0; i < headers.length; i++) { Header header = headers[i]; event.addAttribute(HttpBase.HTTP_EVENT_HEADER_IN + "." + header.getName(), header.getValue()); headerlist.append(header.getName() + (i+1 < headers.length ? "," : "")); } event.addAttribute(HttpBase.HTTP_EVENT_HEADER_IN, headerlist.toString()); event.addAttribute("path", request.getRequestLine().getUri()); if (request instanceof HttpEntityEnclosingRequest) { HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); try { String bw = _listener.getBandwidth(); if ( !_listener.isPerfrun() ) { OutputStream os = new ByteArrayOutputStream(); if ( bw != null ) os = Throttler.wrapOutputStream(os, bw); entity.writeTo(os); event.addAttribute(HttpBase.HTTP_EVENT_BODY, os.toString()); } else { // sha1 of the object. InputStream is = entity.getContent(); if ( bw != null ) is = Throttler.wrapInputStream(is, bw); MessageDigest md = null; boolean calchash = false; if (!_listener.getHash().equals("none")) { md = MessageDigest.getInstance(_listener.getHash()); calchash = true; } int totalRead = 0; int read = 0; byte[] buffer = new byte[4 * 1024]; while ((read = is.read(buffer)) != -1) { if (calchash) md.update(buffer, 0, read); totalRead += read; } event.addAttribute(HttpBase.HTTP_EVENT_BODY_SIZE, totalRead); if (calchash) { event.addAttribute(HttpBase.HTTP_EVENT_BODY_HASH, HashUtil.convertToHex(md.digest())); event.addAttribute(HttpBase.HTTP_EVENT_HASH_ALGO, _listener.getHash()); } } } catch (IOException e) { throw new HttpException("Error handling stream.", e); } catch (NoSuchAlgorithmException e) { throw new HttpException("Error handling stream.", e); } catch (ParseException e) { throw new HttpException("Error handling stream.", e); } } if ( method.equals(_method) || _method.equals("*") ) { ActionState as = ActionState.getInstance(); as.setState(_state); try { _state.getRecorder().record(event); _listener.execute(); Integer rc = (Integer) Action.getContext(Http_response.HTTP_RESPONSE_CODE); String msg = (String) Action.getContext(Http_response.HTTP_RESPONSE_MSG); if (rc != null) response.setStatusCode(rc); else response.setStatusCode(HttpStatus.SC_OK); if ( msg != null ) response.setReasonPhrase(msg); ArrayList<com.yahoo.dtf.actions.http.Header> rheaders = (ArrayList<com.yahoo.dtf.actions.http.Header>) Action.getContext(Http_response.HTTP_RESPONSE_HEADERS); if ( rheaders != null ) { for (int i = 0; i < rheaders.size(); i++) { com.yahoo.dtf.actions.http.Header header = rheaders.get(i); response.setHeader(header.getName(),header.getValue()); } } ArrayList<com.yahoo.dtf.actions.http.cookies.Cookie> rcookies = (ArrayList<com.yahoo.dtf.actions.http.cookies.Cookie>) Action.getContext(Http_response.HTTP_RESPONSE_COOKIES); if ( rcookies != null ) { for (int i = 0; i < rcookies.size(); i++) { com.yahoo.dtf.actions.http.cookies.Cookie cookie = rcookies.get(i); } } long size = 0; Http_response cresponse = (Http_response) _listener.findFirstAction(Http_response.class); if ( cresponse != null ) { Entity entity = (Entity) Action.getContext(Http_response.HTTP_RESPONSE_ENTITY); if ( entity != null ) { DTFInputStream dtfis = entity.getEntityStream(); size = dtfis.getSize(); InputStream is = dtfis; String bw = cresponse.getBandwidth(); if ( bw != null ) is = Throttler.wrapInputStream(is, bw); HttpEntity rentity = new InputStreamEntity(is, size); response.setEntity(rentity); } } response.setHeader("Content-Length", "" + size); } catch (DTFException e) { response.setStatusCode(HttpStatus.SC_BAD_REQUEST); response.setReasonPhrase(e.getMessage()); response.setHeader("Content-Length", "0"); } finally { as.delState(); } } else { response.setStatusCode(HttpStatus.SC_BAD_REQUEST); response.setReasonPhrase("Invalid request there isn't a " + method + " listener at " + target); response.setHeader("Content-Length", "0"); } } }