// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com>
package com.iwebpp.node.http;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import com.iwebpp.node.NodeContext;
import com.iwebpp.node.Util;
import com.iwebpp.node.net.AbstractSocket;
public final class ServerResponse
extends OutgoingMessage {
private final static String TAG = "ServerResponse";
private boolean _sent100;
private String statusMessage;
private boolean _expect_continue;
private Listener onServerResponseClose;
public ServerResponse(NodeContext context, IncomingMessage req) {
super(context);
this.statusCode = 200;
this.statusMessage = null;
if (req.method() == "HEAD") this._hasBody = false;
this.sendDate = true;
if (req.getHttpVersionMajor() < 1 || req.getHttpVersionMinor() < 1) {
// TBD...
///this.useChunkedEncodingByDefault = http.chunkExpression.test(req.headers.te);
this.useChunkedEncodingByDefault = (req.getHeaders().containsKey("te") && Pattern.matches(http.chunkExpression, req.getHeaders().get("te").get(0)));
this.shouldKeepAlive = false;
}
}
private ServerResponse(){super(null);}
protected void _finish() throws Exception {
///DTRACE_HTTP_SERVER_RESPONSE(this.connection);
///COUNTER_HTTP_SERVER_RESPONSE();
super._finish();
}
// response.setTimeout(msecs, callback)
// Event listeners
public void onClose(final closeListener cb) throws Exception {
this.on("close", new Listener(){
@Override
public void onEvent(Object data) throws Exception {
cb.onClose();
}
});
}
public interface closeListener {
public void onClose() throws Exception;
}
public void onFinish(final finishListener cb) throws Exception {
this.on("finish", new Listener(){
@Override
public void onEvent(Object data) throws Exception {
cb.onFinish();
}
});
}
public interface finishListener {
public void onFinish() throws Exception;
}
public void assignSocket(final AbstractSocket socket) throws Exception {
assert(null == socket.get_httpMessage());
socket.set_httpMessage(this);
onServerResponseClose = new Listener() {
@Override
public void onEvent(Object data) throws Exception {
// EventEmitter.emit makes a copy of the 'close' listeners array before
// calling the listeners. detachSocket() unregisters onServerResponseClose
// but if detachSocket() is called, directly or indirectly, by a 'close'
// listener, onServerResponseClose is still in that copy of the listeners
// array. That is, in the example below, b still gets called even though
// it's been removed by a:
//
// var obj = new events.EventEmitter;
// obj.on('event', a);
// obj.on('event', b);
// function a() { obj.removeListener('event', b) }
// function b() { throw "BAM!" }
// obj.emit('event'); // throws
//
// Ergo, we need to deal with stale 'close' events and handle the case
// where the ServerResponse object has already been deconstructed.
// Fortunately, that requires only a single if check. :-)
if (socket.get_httpMessage()!=null) ((ServerResponse)(socket.get_httpMessage())).emit("close");
}
};
socket.on("close", onServerResponseClose);
this.socket = socket;
this.connection = socket;
this.emit("socket", socket);
this._flush();
}
public void detachSocket(AbstractSocket socket) {
assert(socket.get_httpMessage() == this);
socket.removeListener("close", onServerResponseClose);
socket.set_httpMessage(null);
this.socket = this.connection = null;
}
public void writeContinue(WriteCB cb) throws Exception {
this._writeRaw("HTTP/1.1 100 Continue" + http.CRLF + http.CRLF, "utf-8", cb);
this._sent100 = true;
}
protected void _implicitHeader() throws Exception {
this.writeHead(this.statusCode, null, null);
}
public void writeHead(int statusCode, String statusMessage, Map<String, List<String>> obj) throws Exception {
Map<String, List<String>> headers;
debug(TAG, "..... -1");
if (Util.zeroString(statusMessage)) {
this.statusMessage = http.STATUS_CODES.containsKey(statusCode) ?
http.STATUS_CODES.get(statusCode) : "unknown";
} else {
this.statusMessage = statusMessage;
}
this.statusCode = statusCode;
if (this._headers != null) {
debug(TAG, "..... -2");
// Slow-case: when progressive API and header fields are passed.
if (obj != null) {
for (Entry<String, List<String>> entry : obj.entrySet())
if (!Util.zeroString(entry.getKey()))
this.setHeader(entry.getKey(), entry.getValue());
}
// only progressive api is used
headers = this._renderHeaders();
} else {
// only writeHead() called
headers = obj;
}
String statusLine = "HTTP/1.1 " + statusCode + " " +
this.statusMessage + http.CRLF;
if (statusCode == 204 || statusCode == 304 ||
(100 <= statusCode && statusCode <= 199)) {
// RFC 2616, 10.2.5:
// The 204 response MUST NOT include a message-body, and thus is always
// terminated by the first empty line after the header fields.
// RFC 2616, 10.3.5:
// The 304 response MUST NOT contain a message-body, and thus is always
// terminated by the first empty line after the header fields.
// RFC 2616, 10.1 Informational 1xx:
// This class of status code indicates a provisional response,
// consisting only of the Status-Line and optional headers, and is
// terminated by an empty line.
this._hasBody = false;
}
// don't keep alive connections where the client expects 100 Continue
// but we sent a final status; they may put extra bytes on the wire.
if (this.is_expect_continue() && !this._sent100) {
setShouldKeepAlive(false);
}
debug(TAG, "..... -3");
this._storeHeader(statusLine, headers);
}
public void writeHeader(int statusCode, String statusMessage, Map<String, List<String>> headers) throws Exception {
this.writeHead(statusCode, statusMessage, headers);
}
public void writeHead(int statusCode, Map<String, List<String>> headers) throws Exception {
this.writeHead(statusCode, null, headers);
}
public void writeHead(int statusCode) throws Exception {
this.writeHead(statusCode, null, null);
}
public boolean sendDate() {
return super.sendDate;
}
public int statusCode() {
return super.statusCode;
}
/**
* @return the _expect_continue
*/
public boolean is_expect_continue() {
return _expect_continue;
}
/**
* @param _expect_continue the _expect_continue to set
*/
public void set_expect_continue(boolean _expect_continue) {
this._expect_continue = _expect_continue;
}
}