| package org.apache.coyote.lite; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.coyote.ActionCode; |
| import org.apache.coyote.ActionHook; |
| import org.apache.coyote.Adapter; |
| import org.apache.coyote.InputBuffer; |
| import org.apache.coyote.OutputBuffer; |
| import org.apache.coyote.ProtocolHandler; |
| import org.apache.coyote.Request; |
| import org.apache.coyote.Response; |
| import org.apache.tomcat.lite.http.HttpClient; |
| import org.apache.tomcat.lite.http.HttpChannel; |
| import org.apache.tomcat.lite.http.HttpConnectionPool; |
| import org.apache.tomcat.lite.http.HttpConnector; |
| import org.apache.tomcat.lite.http.HttpRequest; |
| import org.apache.tomcat.lite.http.HttpResponse; |
| import org.apache.tomcat.lite.http.HttpServer; |
| import org.apache.tomcat.lite.http.MultiMap; |
| import org.apache.tomcat.lite.http.HttpChannel.HttpService; |
| import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer; |
| import org.apache.tomcat.lite.http.HttpConnector.HttpChannelEvents; |
| import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; |
| import org.apache.tomcat.lite.http.MultiMap.Entry; |
| import org.apache.tomcat.lite.io.CBuffer; |
| import org.apache.tomcat.lite.io.IOConnector; |
| import org.apache.tomcat.lite.io.SocketConnector; |
| import org.apache.tomcat.lite.io.SslProvider; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| import org.apache.tomcat.util.modeler.Registry; |
| |
| /** |
| * Work in progress - use the refactored http as a coyote connector. |
| * Just basic requests work right now - need to implement all the |
| * methods of coyote. |
| * |
| * |
| * @author Costin Manolache |
| */ |
| public class LiteProtocolHandler implements ProtocolHandler { |
| |
| Adapter adapter; |
| Map<String, Object> attributes = new HashMap<String, Object>(); |
| |
| |
| HttpConnector httpConnServer; |
| int port = 8999; |
| |
| // Tomcat JMX integration |
| Registry registry; |
| |
| public LiteProtocolHandler() { |
| } |
| |
| @Override |
| public void destroy() throws Exception { |
| } |
| |
| @Override |
| public Adapter getAdapter() { |
| return adapter; |
| } |
| |
| @Override |
| public Object getAttribute(String name) { |
| // TODO: dynamic |
| return attributes.get(name); |
| } |
| |
| @Override |
| public Iterator<String> getAttributeNames() { |
| return attributes.keySet().iterator(); |
| } |
| |
| @Override |
| public void init() throws Exception { |
| registry = Registry.getRegistry(null, null); |
| httpConnServer = HttpServer.newServer(port); |
| |
| httpConnServer.getDispatcher().setDefaultService(new HttpService() { |
| @Override |
| public void service(HttpRequest httpReq, HttpResponse httpRes) |
| throws IOException { |
| coyoteService(httpReq, httpRes); |
| } |
| |
| }); |
| final String base = "" + port; |
| bind("Httpconnector-" + port, httpConnServer); |
| bind("HttpconnectorPool-" + port, httpConnServer.cpool); |
| IOConnector io = httpConnServer.getIOConnector(); |
| int ioLevel = 0; |
| while (io != null) { |
| bind("IOConnector-" + (ioLevel++) + "-" + base, io); |
| if (io instanceof SocketConnector) { |
| bind("NioThread-" + base, |
| ((SocketConnector) io).getSelector()); |
| |
| } |
| io = io.getNet(); |
| } |
| httpConnServer.cpool.setEvents(new HttpConnectionPool.HttpConnectionPoolEvents() { |
| |
| @Override |
| public void closedConnection(RemoteServer host, HttpConnection con) { |
| unbind("HttpConnection-" + base + "-" + con.getId()); |
| } |
| |
| @Override |
| public void newConnection(RemoteServer host, HttpConnection con) { |
| bind("HttpConnection-" + base + "-" + con.getId(), con); |
| } |
| |
| @Override |
| public void newTarget(RemoteServer host) { |
| bind("AsyncHttp-" + base + "-" + host.target, host); |
| } |
| |
| @Override |
| public void targetRemoved(RemoteServer host) { |
| unbind("AsyncHttp-" + base + "-" + host.target); |
| } |
| |
| }); |
| |
| httpConnServer.setOnCreate(new HttpChannelEvents() { |
| @Override |
| public void onCreate(HttpChannel data, HttpConnector extraData) |
| throws IOException { |
| bind("AsyncHttp-" + base + "-" + data.getId(), data); |
| } |
| @Override |
| public void onDestroy(HttpChannel data, HttpConnector extraData) |
| throws IOException { |
| unbind("AsyncHttp-" + base + "-" + data.getId()); |
| } |
| }); |
| |
| // TODO: process attributes via registry !! |
| |
| } |
| |
| private void bind(String name, Object o) { |
| try { |
| registry.registerComponent(o, "TomcatLite:name=" + name, null); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void unbind(String name) { |
| registry.unregisterComponent("name=" + name); |
| } |
| |
| @Override |
| public void pause() throws Exception { |
| } |
| |
| @Override |
| public void resume() throws Exception { |
| } |
| |
| @Override |
| public void setAdapter(Adapter adapter) { |
| this.adapter = adapter; |
| |
| } |
| |
| @Override |
| public void setAttribute(String name, Object value) { |
| attributes.put(name, value); |
| } |
| |
| @Override |
| public void start() throws Exception { |
| httpConnServer.start(); |
| } |
| |
| public void setPort(int port) { |
| this.port = port; |
| } |
| |
| /** |
| * Wrap old tomcat buffer to lite buffer. |
| */ |
| private void wrap(MessageBytes dest, CBuffer buffer) { |
| dest.setChars(buffer.array(), buffer.position(), |
| buffer.length()); |
| } |
| |
| /** |
| * Main lite service method, will wrap to coyote request |
| */ |
| private void coyoteService(final HttpRequest httpReq, final HttpResponse httpRes) { |
| // TODO: reuse, per req |
| RequestData rc = new RequestData(); |
| rc.init(httpReq, httpRes); |
| |
| try { |
| adapter.service(rc.req, rc.res); |
| } catch (Exception e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * ActionHook implementation, include coyote request/response objects. |
| */ |
| public class RequestData implements ActionHook { |
| private final class LiteOutputBuffer implements OutputBuffer { |
| @Override |
| public int doWrite(org.apache.tomcat.util.buf.ByteChunk chunk, |
| Response response) throws IOException { |
| httpRes.getBody().append(chunk.getBuffer(), chunk.getStart(), |
| chunk.getLength()); |
| return chunk.getLength(); |
| } |
| } |
| |
| OutputBuffer outputBuffer = new LiteOutputBuffer(); |
| // TODO: recycle, etc. |
| Request req = new Request(); |
| |
| Response res = new Response(); |
| HttpResponse httpRes; |
| HttpRequest httpReq; |
| |
| InputBuffer inputBuffer = new InputBuffer() { |
| @Override |
| public int doRead(ByteChunk bchunk, Request request) |
| throws IOException { |
| httpReq.getBody().waitData(httpReq.getHttpChannel().getIOTimeout()); |
| int rd = |
| httpReq.getBody().read(bchunk.getBytes(), |
| bchunk.getStart(), bchunk.getBytes().length); |
| if (rd > 0) { |
| bchunk.setEnd(bchunk.getEnd() + rd); |
| } |
| return rd; |
| } |
| }; |
| |
| public RequestData() { |
| req.setInputBuffer(inputBuffer); |
| res.setOutputBuffer(outputBuffer); |
| req.setResponse(res); |
| res.setRequest(req); |
| res.setHook(this); |
| } |
| |
| public void init(HttpRequest httpReq, HttpResponse httpRes) { |
| this.httpRes = httpRes; |
| this.httpReq = httpReq; |
| // TODO: turn http request into a coyote request - copy all fields, |
| // add hooks where needed. |
| |
| wrap(req.decodedURI(), httpReq.decodedURI()); |
| wrap(req.method(), httpReq.method()); |
| wrap(req.protocol(), httpReq.protocol()); |
| wrap(req.requestURI(), httpReq.requestURI()); |
| wrap(req.queryString(), httpReq.queryString()); |
| |
| req.setServerPort(httpReq.getServerPort()); |
| req.serverName().setString(req.localName().toString()); |
| |
| MultiMap mimeHeaders = httpReq.getMimeHeaders(); |
| MimeHeaders coyoteHeaders = req.getMimeHeaders(); |
| for (int i = 0; i < mimeHeaders.size(); i++ ) { |
| Entry entry = mimeHeaders.getEntry(i); |
| MessageBytes val = |
| coyoteHeaders.addValue(entry.getName().toString()); |
| val.setString(entry.getValue().toString()); |
| } |
| } |
| |
| /** |
| * Send an action to the connector. |
| * |
| * @param actionCode Type of the action |
| * @param param Action parameter |
| */ |
| public void action(ActionCode actionCode, Object param) { |
| |
| if (actionCode == ActionCode.ACTION_POST_REQUEST) { |
| commit(); // make sure it's sent - on errors |
| } else if (actionCode == ActionCode.ACTION_COMMIT) { |
| commit(); |
| } else if (actionCode == ActionCode.ACTION_ACK) { |
| // Done automatically by http connector |
| } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) { |
| try { |
| httpReq.send(); |
| } catch (IOException e) { |
| httpReq.getHttpChannel().abort(e); |
| res.setErrorException(e); |
| } |
| |
| } else if (actionCode == ActionCode.ACTION_CLOSE) { |
| // Close |
| |
| // End the processing of the current request, and stop any further |
| // transactions with the client |
| |
| // comet = false; |
| // try { |
| // outputBuffer.endRequest(); |
| // } catch (IOException e) { |
| // // Set error flag |
| // error = true; |
| // } |
| |
| } else if (actionCode == ActionCode.ACTION_RESET) { |
| // Reset response |
| // Note: This must be called before the response is committed |
| httpRes.getBody().clear(); |
| |
| } else if (actionCode == ActionCode.ACTION_CUSTOM) { |
| |
| // Do nothing |
| |
| } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) { |
| req.remoteAddr().setString(httpReq.remoteAddr().toString()); |
| } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) { |
| req.localName().setString(httpReq.localName().toString()); |
| } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) { |
| req.remoteHost().setString(httpReq.remoteHost().toString()); |
| } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) { |
| req.localAddr().setString(httpReq.localAddr().toString()); |
| } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) { |
| req.setRemotePort(httpReq.getRemotePort()); |
| } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) { |
| req.setLocalPort(httpReq.getLocalPort()); |
| } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) { |
| |
| Object sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_CIPHER); |
| req.setAttribute("javax.servlet.request.cipher_suite", sslAtt); |
| |
| sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_KEY_SIZE); |
| req.setAttribute("javax.servlet.request.key_size", sslAtt); |
| |
| sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_SESSION_ID); |
| req.setAttribute("javax.servlet.request.ssl_session", sslAtt); |
| |
| } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) { |
| |
| Object cert = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_CERT); |
| req.setAttribute("javax.servlet.request.X509Certificate", cert); |
| |
| } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) { |
| ByteChunk body = (ByteChunk) param; |
| httpReq.getBody().clear(); |
| try { |
| httpReq.getBody().append(body.getBuffer(), body.getStart(), body.getLength()); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| |
| } else if (actionCode == ActionCode.ACTION_AVAILABLE) { |
| req.setAvailable(httpReq.getBody().available()); |
| } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) { |
| // comet = true; |
| } else if (actionCode == ActionCode.ACTION_COMET_END) { |
| // comet = false; |
| } else if (actionCode == ActionCode.ACTION_COMET_CLOSE) { |
| //no op |
| } else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) { |
| //no op |
| // } else if (actionCode == ActionCode.ACTION_ASYNC_START) { |
| // //TODO SERVLET3 - async |
| // } else if (actionCode == ActionCode.ACTION_ASYNC_COMPLETE) { |
| // //TODO SERVLET3 - async |
| // } else if (actionCode == ActionCode.ACTION_ASYNC_SETTIMEOUT) { |
| // //TODO SERVLET3 - async |
| } |
| |
| |
| } |
| |
| private void commit() { |
| if (res.isCommitted()) |
| return; |
| |
| // TODO: copy headers, fields |
| httpRes.setStatus(res.getStatus()); |
| httpRes.setMessage(res.getMessage()); |
| MultiMap mimeHeaders = httpRes.getMimeHeaders(); |
| MimeHeaders coyoteHeaders = res.getMimeHeaders(); |
| for (int i = 0; i < coyoteHeaders.size(); i++ ) { |
| MessageBytes name = coyoteHeaders.getName(i); |
| MessageBytes val = coyoteHeaders.getValue(i); |
| Entry entry = mimeHeaders.addEntry(name.toString()); |
| entry.getValue().set(val.toString()); |
| } |
| String contentType = res.getContentType(); |
| if (contentType != null) { |
| mimeHeaders.addEntry("Content-Type").getValue().set(contentType); |
| } |
| String contentLang = res.getContentType(); |
| if (contentLang != null) { |
| mimeHeaders.addEntry("Content-Language").getValue().set(contentLang); |
| } |
| long contentLength = res.getContentLengthLong(); |
| if (contentLength != -1) { |
| httpRes.setContentLength(contentLength); |
| } |
| String lang = res.getContentLanguage(); |
| if (lang != null) { |
| httpRes.setHeader("Content-Language", lang); |
| } |
| |
| try { |
| httpReq.send(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| } |
| } |