/** * Copyright (C) Zhang,Yuexiang (xfeep) * */ package nginx.clojure.clj; import static nginx.clojure.MiniConstants.NGX_HTTP_BODY_FILTER_PHASE; import static nginx.clojure.MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE; import static nginx.clojure.MiniConstants.NGX_HTTP_NOT_FOUND; import static nginx.clojure.MiniConstants.NGX_HTTP_NO_CONTENT; import static nginx.clojure.NginxClojureRT.log; import static nginx.clojure.clj.Constants.ASYNC_TAG; import static nginx.clojure.clj.Constants.BODY; import static nginx.clojure.clj.Constants.KNOWN_RESP_HEADERS; import static nginx.clojure.clj.Constants.STATUS; import java.io.Closeable; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import clojure.lang.IFn; import clojure.lang.ISeq; import clojure.lang.Keyword; import clojure.lang.RT; import clojure.lang.Seqable; import nginx.clojure.MiniConstants; import nginx.clojure.NginxChainWrappedInputStream; import nginx.clojure.NginxClojureRT; import nginx.clojure.NginxHeaderHolder; import nginx.clojure.NginxHttpServerChannel; import nginx.clojure.NginxRequest; import nginx.clojure.NginxResponse; import nginx.clojure.NginxSimpleHandler; import nginx.clojure.java.ArrayMap; public class NginxClojureHandler extends NginxSimpleHandler { public static ArrayMap<Keyword, Object> NOT_FOUND_RESPONSE = ArrayMap.create(STATUS, NGX_HTTP_NOT_FOUND); protected static ConcurrentLinkedQueue<LazyRequestMap> pooledRequests = new ConcurrentLinkedQueue<LazyRequestMap>(); protected IFn ringHandler; protected IFn headerFilter; protected StringFacedClojureBodyFilter bodyFilter; public NginxClojureHandler() { } public NginxClojureHandler(IFn ringHandler, IFn headerFilter) { this.ringHandler = ringHandler; this.headerFilter = headerFilter; } public NginxClojureHandler(IFn bodyFilter) { this.bodyFilter = new StringFacedClojureBodyFilter(bodyFilter); } public static String normalizeHeaderNameHelper(Object nameObj) { String name; if (nameObj instanceof String) { name = (String)nameObj; }else if (nameObj instanceof Keyword) { name = ((Keyword)nameObj).getName(); }else { name = nameObj.toString(); } return name; } @Override protected String normalizeHeaderName(Object nameObj) { return normalizeHeaderNameHelper(nameObj); } @Override public NginxRequest makeRequest(long r, long c) { if (r == 0) { return new LazyRequestMap(this, r, null, new Object[0]) { @Override public long nativeCount() { return 0; } }; } int phase = (int)NginxClojureRT.ngx_http_clojure_mem_get_module_ctx_phase(r); LazyRequestMap req; switch (phase) { case NGX_HTTP_HEADER_FILTER_PHASE : req = new LazyFilterRequestMap(this, r, c); break; case NGX_HTTP_BODY_FILTER_PHASE: req = LazyFilterRequestMap.cloneExisted(r, c); if (req == null) { req = new LazyFilterRequestMap(this, r, c); } break; default : req = pooledRequests.poll(); if (req == null) { req = new LazyRequestMap(this, r); }else { req.reset(r, this); } } return req.phase(phase); } @Override protected long defaultChainFlag(NginxResponse response) { if (response instanceof NginxClojureBodyFilterChunkResponse) { return response.isLast() ? MiniConstants.NGX_CHAIN_FILTER_CHUNK_HAS_LAST : MiniConstants.NGX_CHAIN_FILTER_CHUNK_NO_LAST; } return super.defaultChainFlag(response); } @Override public NginxResponse process(NginxRequest req) throws IOException { LazyRequestMap r = (LazyRequestMap)req; try{ Map resp; switch (req.phase()) { case NGX_HTTP_HEADER_FILTER_PHASE: LazyFilterRequestMap freq = (LazyFilterRequestMap)r; resp = (Map) headerFilter.invoke(freq.responseStatus(), freq, freq.responseHeaders()); break; case NGX_HTTP_BODY_FILTER_PHASE: LazyFilterRequestMap breq = (LazyFilterRequestMap) r; NginxChainWrappedInputStream chunk = breq.body; try{ Map chunkedResp = bodyFilter.invoke(breq, chunk, chunk.isLast()); if (!breq.isHijacked()) { return new NginxClojureBodyFilterChunkResponse(breq, chunkedResp); } else { return toNginxResponse(r, ASYNC_TAG); } }finally{ chunk.close(); } default: resp = (Map) ringHandler.invoke(req); } return req.isHijacked() ? toNginxResponse(r, ASYNC_TAG) : toNginxResponse(r, resp); }finally { int bodyIdx = r.index(BODY); if (bodyIdx > 0) { try { Object body = r.element(bodyIdx); if (body != null && body instanceof Closeable) { ((Closeable)body).close(); } } catch (Throwable e) { NginxClojureRT.log.error("can not close Closeable object such as FileInputStream!", e); } } } } public NginxResponse toNginxResponse(NginxRequest req, Object resp) { if (resp == null) { return new NginxClojureResponse(req, NOT_FOUND_RESPONSE ); } if (resp instanceof NginxResponse) { return (NginxResponse) resp; } return new NginxClojureResponse(req, (Map)resp); } @Override public void completeAsyncResponse(NginxRequest req, Object resp) { NginxClojureRT.completeAsyncResponse(req, toNginxResponse(req, resp)); } @Override public NginxHeaderHolder fetchResponseHeaderPusher(String name) { NginxHeaderHolder pusher = KNOWN_RESP_HEADERS.get(name); if (pusher == null) { pusher = new ResponseUnknownHeaderPusher(name); } return pusher; } @Override protected long buildResponseComplexItemBuf(long r, Object item, long preChain) { if ((item instanceof ISeq) || (item instanceof Seqable) || (item instanceof Iterable)) { ISeq seq = RT.seq(item); long chain = preChain; long first = 0; while (seq != null) { Object o = seq.first(); if (o != null) { long rc = buildResponseItemBuf(r, o, chain); if (rc <= 0) { if (rc != -NGX_HTTP_NO_CONTENT) { return rc; } }else { chain = rc; if (first == 0) { first = chain; } } seq = seq.next(); } } return preChain == 0 ? (first == 0 ? -NGX_HTTP_NO_CONTENT : first) : chain; } return super.buildResponseComplexItemBuf(r, item, preChain); } @Override public NginxHttpServerChannel hijack(NginxRequest req, boolean ignoreFilter) { if (log.isDebugEnabled()) { log.debug("#%s: hijack at %s", req.nativeRequest(), req.uri()); } try { if (req.isHijacked()) { NginxHttpServerChannel channel = req.channel(); channel.setIgnoreFilter(ignoreFilter); return channel; } ((LazyRequestMap)req).hijackTag[0] = 1; if (Thread.currentThread() == NginxClojureRT.NGINX_MAIN_THREAD && (req.phase() == -1 || req.phase() == NGX_HTTP_HEADER_FILTER_PHASE || req.phase() == NGX_HTTP_BODY_FILTER_PHASE)) { NginxClojureRT.ngx_http_clojure_mem_inc_req_count(req.nativeRequest(), 1); } return ((LazyRequestMap)req).channel = new NginxHttpServerChannel(req, ignoreFilter); }finally { if (log.isDebugEnabled()) { log.debug("#%s: hijacked at %s, lns:%s", req.nativeRequest(), req.uri(), req.listeners() == null ? 0 : req.listeners().size()); } } } protected void returnToRequestPool(LazyRequestMap lazyRequestMap) { pooledRequests.add(lazyRequestMap); } }