// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com> package com.iwebpp.node.http; import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.iwebpp.node.NodeContext; import com.iwebpp.node.net.AbstractSocket; import com.iwebpp.node.stream.Readable2; public class IncomingMessage extends Readable2 { private final static String TAG = "IncomingMessage"; private Map<String, List<String>> headers; private Map<String, List<String>> trailers; private AbstractSocket socket; private String httpVersion; private boolean complete; private List<String> rawHeaders; private List<String> rawTrailers; private List<Object> _pendings; private String url; private String method; private int statusCode; private String statusMessage; private boolean _consuming; private boolean _dumped; private boolean upgrade; private ClientRequest req; private int httpVersionMajor; private int httpVersionMinor; private IncomingParser parser; private int _pendingIndex; /** * @return the _pendingIndex */ public int get_pendingIndex() { return _pendingIndex; } /** * @return the upgrade */ public boolean isUpgrade() { return upgrade; } /** * @param upgrade the upgrade to set */ public void setUpgrade(boolean upgrade) { this.upgrade = upgrade; } public IncomingMessage(NodeContext context, AbstractSocket socket) { super(context, new Options(-1, null, false, "utf8", false)); debug(TAG, "start ..."); // XXX This implementation is kind of all over the place // When the parser emits body chunks, they go in this list. // _read() pulls them out, and when it finds EOF, it ends. this.socket = socket; this.httpVersion = null; this.setComplete(false); this.setHeaders(new Hashtable<String, List<String>>()); this.rawHeaders = new ArrayList<String>(); this.trailers = new Hashtable<String, List<String>>(); this.rawTrailers = new ArrayList<String>(); this.readable(true); this.set_pendings(new LinkedList<Object>()); this._pendingIndex = 0; // request (server) only this.url = ""; this.setMethod(null); // response (client) only this.setStatusCode(-1); this.setStatusMessage(null); // flag for backwards compatibility grossness. this._consuming = false; // flag for when we decide that this message cannot possibly be // read by the user, so there's no point continuing to handle it. this.set_dumped(false); } private IncomingMessage() {super(null, null);} @Override protected void _read(int size) throws Exception { // We actually do almost nothing here, because the parserOnBody // function fills up our internal buffer directly. However, we // do need to unpause the underlying socket so that it flows. if (this.socket.readable()) readStart(this.socket); } // It's possible that the socket will be destroyed, and removed from // any messages, before ever calling this. In that case, just skip // it, since something else is destroying this connection anyway. public void destroy(String error) throws Exception { if (this.socket != null) this.socket.destroy(error); } protected void _addHeaderLines(List<String> headers, int n) { /*if (headers && headers.length) { var raw, dest; if (this.complete) { raw = this.rawTrailers; dest = this.trailers; } else { raw = this.rawHeaders; dest = this.headers; } raw.push.apply(raw, headers); for (var i = 0; i < n; i += 2) { var k = headers[i]; var v = headers[i + 1]; this._addHeaderLine(k, v, dest); } }*/ if (headers!=null && headers.size()>0) { ///assert(headers.size() == n); List<String> raw; Map<String, List<String>> dest; if (this.isComplete()) { raw = this.rawTrailers; dest = this.trailers; } else { raw = this.rawHeaders; dest = this.getHeaders(); } raw.addAll(headers); for (int i = 0; i < headers.size(); i += 2) { String k = headers.get(i); String v = headers.get(i+1); this._addHeaderLine(k, v, dest); } } } // Add the given (field, value) pair to the message // // Per RFC2616, section 4.2 it is acceptable to join multiple instances of the // same header with a ', ' if the header in question supports specification of // multiple values this way. If not, we declare the first instance the winner // and drop the second. Extended header fields (those beginning with 'x-') are // always joined. ///IncomingMessage.prototype._addHeaderLine = function(field, value, dest) { private void _addHeaderLine(String field, String value, Map<String, List<String>> dest) { field = field.toLowerCase(); ///switch (field) { // Array headers: ///case "set-cookie": if (field == "set-cookie") { ///if (!util.isUndefined(dest[field])) { if (dest.containsKey(field)) { ///dest[field].push(value); dest.get(field).add(value); } else { ///dest[field] = [value]; dest.put(field, new ArrayList<String>()); dest.get(field).add(value); } ///break; } else if ( // list is taken from: // https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp ///case "content-type": field == "content-type" || ///case "content-length": field == "content-length" || //case "user-agent": field == "user-agent" || //case "referer": field == "referer" || //case "host": field == "host" || ///case "authorization": field == "authorization" || ///case "proxy-authorization": field == "proxy-authorization" || ///case "if-modified-since": field == "if-modified-since" || //case "if-unmodified-since": field == "if-unmodified-since" || ///case "from": field == "from" || ///case "location": field == "location" || ///case "max-forwards": field == "max-forwards" ) { // drop duplicates ///if (util.isUndefined(dest[field])) if (!dest.containsKey(field)) { ///dest[field] = value; dest.put(field, new ArrayList<String>()); dest.get(field).add(value); } ///break; ///default: } else { // make comma-separated list ///if (!util.isUndefined(dest[field])) if (dest.containsKey(field)) { ///dest[field] += ", " + value; String nstr = dest.get(field).get(0) + ", " + value; dest.get(field).set(0, nstr); } else { ///dest[field] = value; dest.put(field, new ArrayList<String>()); dest.get(field).add(value); } } } // Call this instead of resume() if we want to just // dump all the data to /dev/null ///IncomingMessage.prototype._dump = function() { public void _dump() throws Exception { if (!this.is_dumped()) { this.set_dumped(true); this.resume(); } } public static void readStart(AbstractSocket socket) throws Exception { ///if (socket && !socket._paused && socket.readable) if (socket!=null && !socket.is_paused() && socket.readable()) socket.resume(); } public static void readStop(AbstractSocket socket) throws Exception { if (socket != null) socket.pause(); } public String httpVersion() { return this.httpVersion; } public void httpVersion(String httpVersion) { this.httpVersion = httpVersion; } public Map<String, List<String>> headers() { return this.getHeaders(); } public Map<String, List<String>> trailers() { return this.trailers; } // message.setTimeout(msecs, callback) public String method() { return this.getMethod(); } public void method(String method) { this.setMethod(method); } public String url() { return this.url; } public void url(String url) { this.url = url; } public int statusCode() { return this.getStatusCode(); } public void statusCode(int statusCode) { this.setStatusCode(statusCode); } public AbstractSocket socket() { return this.socket; } public Object read(int n) throws Exception { this._consuming = true; return super.read(n); } // Event listeners public void onceClose(final closeListener cb) throws Exception { this.once("close", new Listener(){ @Override public void onEvent(Object data) throws Exception { cb.onClose(); } }); } /** * @return the _dumped */ public boolean is_dumped() { return _dumped; } /** * @param _dumped the _dumped to set */ public void set_dumped(boolean _dumped) { this._dumped = _dumped; } /** * @return the complete */ public boolean isComplete() { return complete; } /** * @param complete the complete to set */ public void setComplete(boolean complete) { this.complete = complete; } /** * @return the _pendings */ public List<Object> get_pendings() { return _pendings; } /** * @param _pendings the _pendings to set */ public void set_pendings(List<Object> _pendings) { this._pendings = _pendings; } /** * @return the method */ public String getMethod() { return method; } /** * @param method the method to set */ public void setMethod(String method) { this.method = method; } /** * @return the statusCode */ public int getStatusCode() { return statusCode; } /** * @param statusCode the statusCode to set */ public void setStatusCode(int statusCode) { this.statusCode = statusCode; } /** * @return the statusMessage */ public String getStatusMessage() { return statusMessage; } /** * @param statusMessage the statusMessage to set */ public void setStatusMessage(String statusMessage) { this.statusMessage = statusMessage; } /** * @return the _consuming */ public boolean is_consuming() { return _consuming; } /** * @param _consuming the _consuming to set */ public void set_consuming(boolean _consuming) { this._consuming = _consuming; } /** * @return the httpVersionMajor */ public int getHttpVersionMajor() { return httpVersionMajor; } /** * @param httpVersionMajor the httpVersionMajor to set */ public void setHttpVersionMajor(int httpVersionMajor) { this.httpVersionMajor = httpVersionMajor; } /** * @return the httpVersionMinor */ public int getHttpVersionMinor() { return httpVersionMinor; } /** * @param httpVersionMinor the httpVersionMinor to set */ public void setHttpVersionMinor(int httpVersionMinor) { this.httpVersionMinor = httpVersionMinor; } /** * @return the req */ public ClientRequest getReq() { return req; } /** * @param req the req to set */ public void setReq(ClientRequest req) { this.req = req; } public String getPath() { return this.req!=null? this.req.getPath() : null; } /** * @return the parser */ public IncomingParser getParser() { return parser; } /** * @param parser the parser to set */ public void setParser(IncomingParser parser) { this.parser = parser; } /** * @return the headers */ public Map<String, List<String>> getHeaders() { return headers; } /** * @param headers the headers to set */ public void setHeaders(Map<String, List<String>> headers) { this.headers = headers; } public interface closeListener { public void onClose() throws Exception; } // POJO beans }