// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com>
package com.iwebpp.node.http;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.List;
import com.iwebpp.node.HttpParser;
import com.iwebpp.node.NodeContext;
import com.iwebpp.node.net.AbstractSocket;
public abstract class IncomingParser
extends HttpParser {
private final static String TAG = "IncomingParser";
protected AbstractSocket socket;
protected IncomingMessage incoming;
protected CharsetDecoder decoder;
protected String [] fields_; ///[32]; // header fields
protected String [] values_; ///[32]; // header values
protected String url_;
protected String status_message_;
protected int num_fields_;
protected int num_values_;
protected boolean have_flushed_;
protected ByteBuffer current_buffer_;
protected int maxHeaderPairs;
protected List<String> _headers;
protected String _url;
private NodeContext context;
protected IncomingParser(NodeContext ctx, http_parser_type type, AbstractSocket socket) {
super(type, socket);
this.context = ctx;
// TODO Auto-generated constructor stub
this.decoder = Charset.forName("utf-8").newDecoder();
this.socket = socket;
this._headers = new ArrayList<String>();
this._url = "";
this.fields_ = new String[32];
this.values_ = new String[32];
this.url_ = "";
this.status_message_ = "";
this.num_fields_ = this.num_values_ = 0;
this.have_flushed_ = false;
this.current_buffer_ = null;
}
private IncomingParser(){super(null, null);}
protected void Init(http_parser_type type) {
super.reset(type);
_headers.clear();
_url = "";
url_ = "";
status_message_ = "";
num_fields_ = 0;
num_values_ = 0;
have_flushed_ = false;
current_buffer_ = null;
}
// spill headers and request path to JS land
protected void Flush() {
parserOnHeaders(CreateHeaders(), url_);
///if (r.IsEmpty())
/// got_exception_ = true;
url_ = "";
have_flushed_ = true;
}
protected List<String> CreateHeaders() {
// num_values_ is either -1 or the entry # of the last header
// so num_values_ == 0 means there's a single header
List<String> headers = new ArrayList<String>();
for (int i = 0; i < this.num_values_; i ++) {
headers.add(this.fields_[i]);
headers.add(this.values_[i]);
}
return headers;
}
public void Pause(boolean should_pause) {
pause(should_pause);
}
public void Reinitialize(http_parser_type type) {
Init(type);
}
public int Finish() throws Exception {
int rv = execute(null);
if (rv != 0) {
http_errno err = HTTP_PARSER_ERRNO();
throw new Exception(err.desc());
}
return rv;
}
// var bytesParsed = parser->execute(buffer);
public int Execute(ByteBuffer buffer_obj) throws Exception {
int buffer_len = buffer_obj.capacity();
// This is a hack to get the current_buffer to the callbacks with the least
// amount of overhead. Nothing else will run while http_parser_execute()
// runs, therefore this pointer can be set and used for the execution.
current_buffer_ = buffer_obj;
int nparsed = execute(current_buffer_);
// Unassign the 'buffer_' variable
current_buffer_.clear();
// If there was a parse error in one of the callbacks
// TODO(bnoordhuis) What if there is an error on EOF?
if (!isUpgrade() && nparsed != buffer_len) {
// TBD...
///http_errno err = HTTP_PARSER_ERRNO();
///throw new Exception(err.desc());
return -1;
}
return nparsed;
}
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
// processed in a single run. This method is also
// called to process trailing http headers.
protected void parserOnHeaders(List<String> headers, String url) {
debug(TAG, "parserOnHeaders ");
// Once we exceeded headers limit - stop collecting them
if (this.maxHeaderPairs <= 0 ||
this._headers.size() < this.maxHeaderPairs) {
///this._headers = this._headers.concat(headers);
this._headers.addAll(headers);
}
this._url += url != null ? url : "";
}
// info.headers and info.url are set only if .onHeaders()
// has not been called for this request.
//
// info.url is not set for response parsers but that's not
// applicable here since all our parsers are request parsers.
///function parserOnHeadersComplete(info) {
protected boolean parserOnHeadersComplete(parseInfo info) throws Exception {
///debug('parserOnHeadersComplete', info);
debug(TAG, "parserOnHeadersComplete "+info);
///var parser = this;
List<String> headers = info.headers;
String url = info.url;
if (null == headers || headers.isEmpty()) {
headers = _headers;
_headers.clear();
}
if (null==url || ""==url) {
url = _url;
_url = "";
}
/*parser.incoming = new IncomingMessage(parser.socket);
parser.incoming.httpVersionMajor = info.versionMajor;
parser.incoming.httpVersionMinor = info.versionMinor;
parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor;
parser.incoming.url = url;
*/
// TBD...
this.incoming = new IncomingMessage(context, (AbstractSocket)super.getData());
this.incoming.setHttpVersionMajor(info.versionMajor);
this.incoming.setHttpVersionMinor(info.versionMinor);
this.incoming.httpVersion(info.versionMajor + "." + info.versionMinor);
this.incoming.url(url);
///var n = headers.length;
int n = headers.size();
// If parser.maxHeaderPairs <= 0 - assume that there're no limit
if (maxHeaderPairs > 0) {
n = Math.min(n, maxHeaderPairs);
}
incoming._addHeaderLines(headers, n);
if (super.getType() == http_parser_type.HTTP_REQUEST/*isNumber(info.method)*/) {
// server only
incoming.setMethod(info.method.desc()) ;
} else {
// client only
incoming.setStatusCode(info.statusCode);
incoming.setStatusMessage(info.statusMessage);
}
incoming.setUpgrade(info.upgrade);
boolean skipBody = false; // response to HEAD or CONNECT
if (!info.upgrade) {
// For upgraded connections and CONNECT method request,
// we'll emit this after parser.execute
// so that we can capture the first part of the new protocol
skipBody = onIncoming(incoming, info.shouldKeepAlive);
}
return skipBody;
}
// POJO bean
protected class parseInfo {
public boolean shouldKeepAlive;
public boolean upgrade;
public http_method method;
public String url;
public List<String> headers;
public int statusCode;
public String statusMessage;
public int versionMajor;
public int versionMinor;
}
protected abstract boolean onIncoming(IncomingMessage incoming, boolean shouldKeepAlive) throws Exception;
// XXX This is a mess.
// TODO: http.Parser should be a Writable emits request/response events.
///function parserOnBody(b, start, len) {
protected void parserOnBody(ByteBuffer b) throws Exception {
debug(TAG, "parserOnBody ");
IncomingParser parser = this;
IncomingMessage stream = parser.incoming;
// if the stream has already been removed, then drop it.
if (null==stream)
return;
AbstractSocket socket = stream.socket();
int len = b == null ? 0 : b.capacity();
// pretend this was the result of a stream._read call.
if (len > 0 && !stream.is_dumped()) {
///var slice = b.slice(start, start + len);
boolean ret = stream.push(b, null);
if (!ret)
IncomingMessage.readStop(socket);
}
}
///function parserOnMessageComplete() {
protected void parserOnMessageComplete() throws Exception {
debug(TAG, "parserOnMessageComplete ");
IncomingParser parser = this;
IncomingMessage stream = parser.incoming;
if (stream!=null) {
stream.setComplete(true);
// Emit any trailing headers.
List<String> headers = parser._headers;
if (headers!=null && !headers.isEmpty()) {
stream._addHeaderLines(headers, headers.size());
_headers.clear();
_url = "";
}
if (!stream.isUpgrade())
// For upgraded connections, also emit this after parser.execute
stream.push(null, null);
}
if (stream!=null && 0==stream.get_pendings().size()) {
// For emit end event
stream.push(null, null);
}
// force to read the next incoming message
IncomingMessage.readStart(parser.socket);
}
@Override
protected int on_message_begin() throws Exception {
num_fields_ = num_values_ = 0;
url_ = "";
status_message_ = "";
return 0;
}
@Override
protected int on_url(ByteBuffer url) throws Exception {
url_ = decoder.decode(url).toString();
return 0;
}
@Override
protected int on_status(ByteBuffer status) throws Exception {
status_message_ = decoder.decode(status).toString();
return 0;
}
@Override
protected int on_header_field(ByteBuffer field) throws Exception {
if (num_fields_ == num_values_) {
// start of new field name
num_fields_++;
///if (num_fields_ == ARRAY_SIZE(fields_)) {
if (num_fields_ == fields_.length) {
// ran out of space - flush to javascript land
Flush();
num_fields_ = 1;
num_values_ = 0;
}
fields_[num_fields_ - 1] = "";
}
///assert(num_fields_ < static_cast<int>(ARRAY_SIZE(fields_)));
assert(num_fields_ < fields_.length);
assert(num_fields_ == num_values_ + 1);
fields_[num_fields_ - 1] = decoder.decode(field).toString();
return 0;
}
@Override
protected int on_header_value(ByteBuffer value) throws Exception {
if (num_values_ != num_fields_) {
// start of new header value
num_values_++;
values_[num_values_ - 1] = "";
}
assert(num_values_ < values_.length);
assert(num_values_ == num_fields_);
values_[num_values_ - 1] = decoder.decode(value).toString();
return 0;
}
@Override
protected int on_headers_complete() throws Exception {
///Local<Object> message_info = Object::New(env()->isolate());
parseInfo message_info = new parseInfo();
if (have_flushed_) {
// Slow case, flush remaining headers.
Flush();
} else {
// Fast case, pass headers and URL to JS land.
message_info.headers = CreateHeaders();
if (getType() == http_parser_type.HTTP_REQUEST)
message_info.url = url_;
}
num_fields_ = num_values_ = 0;
// METHOD
if (getType() == http_parser_type.HTTP_REQUEST) {
message_info.method = getMethod();
}
// STATUS
if (getType() == http_parser_type.HTTP_RESPONSE) {
message_info.statusCode = getStatus_code();
message_info.statusMessage = status_message_;
}
// VERSION
message_info.versionMajor = super.getHttp_major();
message_info.versionMinor = super.getHttp_minor();
message_info.shouldKeepAlive = super.http_should_keep_alive();
message_info.upgrade = super.isUpgrade();
return parserOnHeadersComplete(message_info) ? 1 : 0;
}
@Override
protected int on_body(ByteBuffer body) throws Exception {
parserOnBody(body);
return 0;
}
@Override
protected int on_message_complete() throws Exception {
if (num_fields_ > 0)
Flush(); // Flush trailing http headers.
parserOnMessageComplete();
return 0;
}
// Free the parser and also break any links that it
// might have to any other things.
// TODO: All parser data should be attached to a
// single object, so that it can be easily cleaned
// up by doing `parser.data = {}`, which should
// be done in FreeList.free. `parsers.free(parser)`
// should be all that is needed.
public static void freeParser(IncomingParser parser, Object req) {
if (parser != null) {
parser._headers.clear();
///parser.onIncoming = null;
if (parser.socket != null)
parser.socket.setParser(null);
parser.socket = null;
parser.incoming = null;
///parsers.free(parser);
parser = null;
}
if (req != null) {
if (req instanceof IncomingMessage)
((IncomingMessage)req).setParser(null);
if (req instanceof ClientRequest)
((ClientRequest)req).setParser(null);
}
}
}