/**
* Copyright (C) Zhang,Yuexiang (xfeep)
*
*/
package nginx.clojure;
import static nginx.clojure.MiniConstants.DEFAULT_ENCODING;
import static nginx.clojure.MiniConstants.NGX_OK;
import static nginx.clojure.NginxClojureRT.log;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import nginx.clojure.java.NginxJavaResponse;
import nginx.clojure.net.NginxClojureAsynSocket;
import sun.nio.ch.DirectBuffer;
public class NginxHttpServerChannel implements Closeable {
protected NginxRequest request;
protected boolean ignoreFilter;
protected volatile boolean closed;
protected Object context;
protected long asyncTimeout;
protected Object closeLock = new Object[0];
private static ChannelListener<NginxHttpServerChannel> closeListener = new ChannelCloseAdapter<NginxHttpServerChannel>() {
@Override
public void onClose(NginxHttpServerChannel sc) {
synchronized (sc.closeLock) {
if (!sc.closed) {
sc.request.uri();//cache uri for logging usage otherwise we can not get uri from a released request
sc.closed = true;
}
}
}
};
public NginxHttpServerChannel(NginxRequest request, boolean ignoreFilter) {
this.request = request;
this.ignoreFilter = ignoreFilter;
request.addListener(this, closeListener);
}
public <T> void addListener(T data, ChannelListener<T> listener) {
this.request.addListener(data, listener);
}
/**
* turn on event handler
* @throws IOException
*/
public void turnOnEventHandler(boolean read, boolean write, boolean nokeepalive) throws IOException {
checkValid();
int flag = 0;
if (read) {
flag |= MiniConstants.NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_READ;
}
if (write) {
flag |= MiniConstants.NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_WRITE;
}
if (nokeepalive) {
flag |= MiniConstants.NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_NOKEEPALIVE;
}
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
final int fflag = flag;
NginxClojureRT.postPollTaskEvent(new Runnable() {
@Override
public void run() {
NginxClojureRT.ngx_http_hijack_turn_on_event_handler(request.nativeRequest(), fflag);
}
});
}else {
NginxClojureRT.ngx_http_hijack_turn_on_event_handler(request.nativeRequest(), flag);
}
}
protected int send(byte[] message, long off, int len, int flag) {
if (message == null) {
return (int)NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, 0, 0, flag);
}
return (int)NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), message, MiniConstants.BYTE_ARRAY_OFFSET + off, len, flag);
}
protected int send(ByteBuffer message, int flag) {
if (message == null) {
return (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, 0, 0, flag);
}
int rc = 0;
if (message.isDirect()) {
rc = (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null,
((DirectBuffer) message).address() + message.position(), message.remaining(), flag);
} else {
rc = (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), message.array(),
MiniConstants.BYTE_ARRAY_OFFSET + message.arrayOffset()+message.position(), message.remaining(), flag);
}
if (rc == MiniConstants.NGX_OK) {
message.position(message.limit());
}
return rc;
}
private final void checkValid() throws IOException {
if (closed) {
throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request);
}
}
/**
* If message is null when flush is true it will do flush, when last is true it will close channel.
*/
public void send(byte[] message, int off, int len, boolean flush, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
int flag = computeFlag(flush, last);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendEvent(this, message == null ? null : Arrays.copyOfRange(message, off, off + len), 0,
len, flag);
}else {
send(message, off, len, flag);
}
}
public int computeFlag(boolean flush, boolean last) {
int flag = 0;
if (flush) {
flag |= MiniConstants.NGX_CLOJURE_BUF_FLUSH_FLAG;
}
if (last) {
flag |= MiniConstants.NGX_CLOJURE_BUF_LAST_FLAG;
}
if (ignoreFilter) {
flag |= MiniConstants.NGX_CLOJURE_BUF_IGNORE_FILTER_FLAG;
}
return flag;
}
public void flush() throws IOException {
checkValid();
int flag = computeFlag(true, false);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendEvent(this, null, 0, 0, flag);
}else {
send(null, 0, 0, flag);
}
}
public void send(String message, boolean flush, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
if (log.isDebugEnabled()) {
log.debug("#%s: send message : '%s', len=%s, flush=%s, last=%s, lns=%s", request.nativeRequest(),
HackUtils.truncateToDotAppendString(message, 10), message == null ? "<NULL>" : message.length(), flush, last,
request.listeners() == null ? 0 : request.listeners().size());
}
byte[] bs = message == null ? null : message.getBytes(DEFAULT_ENCODING);
int flag = computeFlag(flush, last) | MiniConstants.NGX_CLOJURE_BUF_APP_MSGTXT;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendEvent(this, bs, 0, bs == null ? 0 : bs.length, flag);
}else {
send(bs, 0, bs == null ? 0 : bs.length, flag);
}
}
public void send(ByteBuffer message, boolean flush, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
if (log.isDebugEnabled()) {
log.debug("#%s: send message : '%s', flush=%s, last=%s, lns=%s", request.nativeRequest(), message, flush, last, request.listeners() == null ? 0 : request.listeners().size());
}
int flag = computeFlag(flush, last);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
if (message != null) {
ByteBuffer cm = ByteBuffer.allocate(message.remaining());
cm.put(message);
cm.flip();
NginxClojureRT.postHijackSendEvent(this, cm, 0, cm.remaining(), flag);
}else {
NginxClojureRT.postHijackSendEvent(this, null, 0, 0, flag);
}
}else {
send(message, flag);
}
}
public long read(ByteBuffer buf) throws IOException {
long rc = 0;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
synchronized (closeLock) {
if (closed) {
throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request);
}
if (buf.isDirect()) {
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), null,
((DirectBuffer) buf).address() + buf.position(), buf.remaining());
} else {
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), buf.array(), MiniConstants.BYTE_ARRAY_OFFSET
+ buf.arrayOffset() + buf.position(), buf.remaining());
}
}
}else {
if (closed) {
throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request);
}
if (buf.isDirect()) {
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), null,
((DirectBuffer) buf).address() + buf.position(), buf.remaining());
} else {
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), buf.array(), MiniConstants.BYTE_ARRAY_OFFSET
+ buf.arrayOffset() + buf.position(), buf.remaining());
}
}
if (NginxClojureRT.log.isDebugEnabled()) {
NginxClojureRT.log.debug("NginxHttpServerChannel read rc=%d", rc);
}
if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
return 0;
}else if (rc == 0) {
return -1;
}else if (rc < 0) {
throw new IOException(NginxClojureAsynSocket.errorCodeToString(rc));
}else {
buf.position(buf.position() + (int)rc);
}
return rc;
}
public long read(byte[] buf, long off, long size) throws IOException {
long rc;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
synchronized (closeLock) {
if (closed) {
throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request);
}
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), buf, MiniConstants.BYTE_ARRAY_OFFSET + off, size);
}
}else {
if (closed) {
throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request);
}
rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), buf, MiniConstants.BYTE_ARRAY_OFFSET + off, size);
}
if (NginxClojureRT.log.isDebugEnabled()) {
NginxClojureRT.log.debug("NginxHttpServerChannel read rc=%d", rc);
}
if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
return 0;
}else if (rc == 0) {
return -1;
}else if (rc < 0) {
throw new IOException(NginxClojureAsynSocket.errorCodeToString(rc));
}
return rc;
}
protected long unsafeWrite(byte[] buf, long off, long size) {
return NginxClojureRT.ngx_http_hijack_write(request.nativeRequest(), buf, MiniConstants.BYTE_ARRAY_OFFSET + off, size);
}
protected long unsafeWrite(ByteBuffer buf) {
long rc;
if (buf.isDirect()) {
rc = NginxClojureRT.ngx_http_hijack_write(request.nativeRequest(), null,
((DirectBuffer) buf).address() + buf.position(), buf.remaining());
}else {
rc = NginxClojureRT.ngx_http_hijack_write(request.nativeRequest(), buf.array(),
MiniConstants.BYTE_ARRAY_OFFSET + buf.arrayOffset() + buf.position(), buf.remaining());
}
if (rc > 0) {
buf.position(buf.position() + (int)rc);
}
return rc;
}
public long write(byte[] buf, long off, int size) throws IOException {
checkValid();
long rc;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
rc = NginxClojureRT.postHijackWriteEvent(this, buf, off, size);
}else {
rc = unsafeWrite(buf, off, size);
}
if (NginxClojureRT.log.isDebugEnabled()) {
NginxClojureRT.log.debug("NginxHttpServerChannel write rc=%d", rc);
}
if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
return 0;
}else if (rc == 0) {
return -1;
}else if (rc < 0) {
throw new IOException(NginxClojureAsynSocket.errorCodeToString(rc));
}
return (int)rc;
}
public long write(ByteBuffer buf) throws IOException {
checkValid();
long rc;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
rc = NginxClojureRT.postHijackWriteEvent(this, buf, 0, buf.remaining());
}else {
rc = unsafeWrite(buf);
}
if (NginxClojureRT.log.isDebugEnabled()) {
NginxClojureRT.log.debug("NginxHttpServerChannel write rc=%d", rc);
}
if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
return 0;
}else if (rc == 0) {
return -1;
}else if (rc < 0) {
throw new IOException(NginxClojureAsynSocket.errorCodeToString(rc));
}
return rc;
}
protected void sendHeader(int flag) {
NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), flag);
}
protected int sendHeader(byte[] message, long off, int len, int flag) {
int rc = (int)NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), message, MiniConstants.BYTE_ARRAY_OFFSET + off, len, flag);
if (rc < 0) {
NginxClojureRT.log.error("bad header from server : %s", new String(message));
}
return rc;
}
protected int sendHeader(ByteBuffer message, int flag) {
int rc = 0;
if (message.isDirect()) {
rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), null,
((DirectBuffer) message).address() + message.position(), message.remaining(), flag);
} else {
rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), message.array(),
MiniConstants.BYTE_ARRAY_OFFSET + message.arrayOffset()+message.position(), message.remaining(), flag);
}
if (rc == MiniConstants.NGX_OK) {
message.position(message.limit());
}else if (rc < 0) {
NginxClojureRT.log.error("bad header from server : %s", HackUtils.decode(message, DEFAULT_ENCODING, NginxClojureRT.pickCharBuffer()));
}
return rc;
}
public <K, V> void sendHeader(long status, Collection<Map.Entry<K, V>> headers, boolean flush, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
int flag = computeFlag(flush, last);
request.handler().prepareHeaders(request, status, headers);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendHeaderEvent(this, flag);
return;
}else {
sendHeader(flag);
}
}
public void sendHeader(byte[] buf, int pos, int len, boolean flush, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
int flag = computeFlag(flush, last);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendHeaderEvent(this, buf, pos, len, flag);
return;
}else {
sendHeader(buf, pos, len, flag);
}
}
protected long sendResponseHelp(NginxResponse resp, long chain) {
NginxRequest req = resp.request();
if (req.isReleased()) {
if (resp.type() > 0) {
log.error("#%d: request is release! and we alos meet an unhandled exception! %s", req.nativeRequest(), resp.fetchBody());
}else {
log.error("#%d: request is release! ", req.nativeRequest());
}
return MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR;
}
long rc = NGX_OK;
long r = req.nativeRequest();
if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) {
if (req.phase() == MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE) {
rc = NginxClojureRT.ngx_http_filter_continue_next(r, -1);
NginxClojureRT.ngx_http_finalize_request(r, rc);
return NGX_OK;
}
// if (req.isHijacked()) {
// //decrease r->count
// NginxClojureRT.ngx_http_finalize_request(r, rc);
// }
NginxClojureRT.ngx_http_clojure_mem_continue_current_phase(r, MiniConstants.NGX_DECLINED);
return NGX_OK;
}
int phase = req.phase();
long nr = req.nativeRequest();
if (chain < 0) {
req.handler().prepareHeaders(req, -(int)chain, resp.fetchHeaders());
rc = -chain;
}else if (chain == 0) {
rc = MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR;
} else {
int status = resp.fetchStatus(MiniConstants.NGX_HTTP_OK);
if (phase == MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE) {
NginxClojureRT.ngx_http_clear_header_and_reset_ctx_phase(nr, ~phase);
}
req.handler().prepareHeaders(req, status, resp.fetchHeaders());
rc = NginxClojureRT.ngx_http_hijack_send_header(r, computeFlag(false, false));
if (rc == MiniConstants.NGX_ERROR || rc > NGX_OK) {
}else {
//close will be done by handleReturnCodeFromHandler, so we do not need pass close flag
rc = NginxClojureRT.ngx_http_hijack_send_chain(r, chain, computeFlag(true, false));
if (rc == NGX_OK && phase != -1) {
NginxClojureRT.ngx_http_ignore_next_response(nr);
}
if (phase != -1) {
if (phase == MiniConstants.NGX_HTTP_ACCESS_PHASE || phase == MiniConstants.NGX_HTTP_REWRITE_PHASE ) {
rc = NginxClojureRT.handleReturnCodeFromHandler(nr, phase, rc, status);
}else {
NginxClojureRT.handleReturnCodeFromHandler(nr, phase, rc, status);
}
}
}
}
if (phase == -1 || phase == MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE) {
NginxClojureRT.ngx_http_finalize_request(r, rc);
}else if (rc != MiniConstants.NGX_DONE){
NginxClojureRT.ngx_http_clojure_mem_continue_current_phase(r, rc);
}
return NGX_OK;
}
public void sendResponse(Object resp) throws IOException {
checkValid();
NginxResponse response = request.handler().toNginxResponse(request, resp);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendResponseEvent(this, response, request.handler().buildOutputChain(response));
}else {
sendResponseHelp(response, request.handler().buildOutputChain(response));
}
}
public void sendBody(final Object body, boolean last) throws IOException {
checkValid();
if (last) {
closed = true;
}
NginxResponse tmpResp = new NginxSimpleResponse(request) {
@Override
public Object fetchBody() {
return body;
}
@Override
public <K, V> Collection<Entry<K, V>> fetchHeaders() {
return Collections.EMPTY_LIST;
}
@Override
public int fetchStatus(int defaultStatus) {
return 200;
}
};
long chain = ((NginxSimpleHandler)request.handler()).buildResponseItemBuf(request.nativeRequest(), body, 0);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postHijackSendResponseEvent(this, tmpResp, chain);
}else {
NginxClojureRT.ngx_http_hijack_send_chain(request.nativeRequest(), chain, computeFlag(false, last));
}
}
public void sendResponse(int status) throws IOException {
checkValid();
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxResponse response = new NginxJavaResponse(request, new Object[]{status, null, null});
NginxClojureRT.postHijackSendResponseEvent(this, response, request.handler().buildOutputChain(response));
}else {
closed = true;
NginxClojureRT.ngx_http_finalize_request(request.nativeRequest(), status);
}
}
public void close() throws IOException {
int flag = computeFlag(false, true);
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
NginxClojureRT.postHijackSendEvent(this, null, 0,
0, flag);
}else {
if (closed) {
return;
}
closed = true;
send(null, 0, 0, flag);
}
}
public void tagClose() {
closed = true;
}
public boolean isIgnoreFilter() {
return ignoreFilter;
}
public void setIgnoreFilter(boolean ignoreFilter) {
this.ignoreFilter = ignoreFilter;
}
public NginxRequest request() {
return request;
}
public boolean isClosed() {
return closed;
}
public Object getContext() {
return context;
}
public void setContext(Object context) {
this.context = context;
}
public long getAsyncTimeout() {
return asyncTimeout;
}
public void setAsyncTimeout(final long asyncTimeout) throws IOException {
checkValid();
this.asyncTimeout = asyncTimeout;
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
NginxClojureRT.postPollTaskEvent(new Runnable() {
@Override
public void run() {
NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout);
}
});
}else {
NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout);
}
}
public boolean webSocketUpgrade(final boolean sendErrorForNonWebSocket) {
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
FutureTask<Boolean> task = new FutureTask<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0;
}
});
NginxClojureRT.postPollTaskEvent(task);
try {
return task.get();
} catch (InterruptedException e) {
throw new RuntimeException("webSocketUpgrade Interrupted", e);
} catch (ExecutionException e) {
throw new RuntimeException("webSocketUpgrade Execution error", e.getCause());
}
}else {
return NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0;
}
}
}