/** * Copyright (C) Zhang,Yuexiang (xfeep) * */ package nginx.clojure.clj; import static nginx.clojure.MiniConstants.BODY_FETCHER; import static nginx.clojure.MiniConstants.CHARACTER_ENCODING_FETCHER; import static nginx.clojure.MiniConstants.CONTENT_TYPE_FETCHER; import static nginx.clojure.MiniConstants.DEFAULT_ENCODING; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_HEADERSI_HEADERS_OFFSET; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_REQ_HEADERS_IN_OFFSET; import static nginx.clojure.MiniConstants.QUERY_STRING_FETCHER; import static nginx.clojure.MiniConstants.REMOTE_ADDR_FETCHER; import static nginx.clojure.MiniConstants.SCHEME_FETCHER; import static nginx.clojure.MiniConstants.SERVER_NAME_FETCHER; import static nginx.clojure.MiniConstants.SERVER_PORT_FETCHER; import static nginx.clojure.MiniConstants.URI_FETCHER; import static nginx.clojure.clj.Constants.BODY; import static nginx.clojure.clj.Constants.CHARACTER_ENCODING; import static nginx.clojure.clj.Constants.CONTENT_TYPE; import static nginx.clojure.clj.Constants.HEADERS; import static nginx.clojure.clj.Constants.HEADER_FETCHER; import static nginx.clojure.clj.Constants.QUERY_STRING; import static nginx.clojure.clj.Constants.REMOTE_ADDR; import static nginx.clojure.clj.Constants.REQUEST_METHOD; import static nginx.clojure.clj.Constants.REQUEST_METHOD_FETCHER; import static nginx.clojure.clj.Constants.SCHEME; import static nginx.clojure.clj.Constants.SERVER_NAME; import static nginx.clojure.clj.Constants.SERVER_PORT; import static nginx.clojure.clj.Constants.URI; import static nginx.clojure.clj.Constants.WEBSOCKET; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import nginx.clojure.ChannelListener; import nginx.clojure.Coroutine; import nginx.clojure.NginxClojureRT; import nginx.clojure.NginxHandler; import nginx.clojure.NginxHttpServerChannel; import nginx.clojure.NginxRequest; import nginx.clojure.RequestVarFetcher; import nginx.clojure.Stack; import nginx.clojure.UnknownHeaderHolder; import nginx.clojure.java.NginxJavaRequest; import nginx.clojure.java.RequestRawMessageAdapter; import nginx.clojure.java.RequestRawMessageAdapter.RequestOrderedRunnable; import nginx.clojure.net.NginxClojureAsynSocket; import clojure.lang.AFn; import clojure.lang.ASeq; import clojure.lang.Counted; import clojure.lang.IMapEntry; import clojure.lang.IPersistentCollection; import clojure.lang.IPersistentMap; import clojure.lang.IPersistentVector; import clojure.lang.ISeq; import clojure.lang.MapEntry; import clojure.lang.Obj; import clojure.lang.RT; import clojure.lang.Util; public class LazyRequestMap extends AFn implements NginxRequest, IPersistentMap { protected final static Object[] default_request_array = new Object[] { URI, URI_FETCHER, BODY, BODY_FETCHER, HEADERS, HEADER_FETCHER, SERVER_PORT,SERVER_PORT_FETCHER, SERVER_NAME, SERVER_NAME_FETCHER, REMOTE_ADDR, REMOTE_ADDR_FETCHER, QUERY_STRING, QUERY_STRING_FETCHER, SCHEME, SCHEME_FETCHER, REQUEST_METHOD, REQUEST_METHOD_FETCHER, CONTENT_TYPE, CONTENT_TYPE_FETCHER, CHARACTER_ENCODING, CHARACTER_ENCODING_FETCHER, WEBSOCKET, new RequestVarFetcher() { public Object fetch(long r, Charset encoding) { return "websocket".equals(new UnknownHeaderHolder("Upgrade", NGX_HTTP_CLOJURE_HEADERSI_HEADERS_OFFSET).fetch(r+NGX_HTTP_CLOJURE_REQ_HEADERS_IN_OFFSET)); } } }; public static void fixDefaultRequestArray() { if (default_request_array[1] == null) { int i = 0; default_request_array[(i++ << 1) + 1] = URI_FETCHER; default_request_array[(i++ << 1) + 1] = BODY_FETCHER; default_request_array[(i++ << 1) + 1] = HEADER_FETCHER; default_request_array[(i++ << 1) + 1] = SERVER_PORT_FETCHER; default_request_array[(i++ << 1) + 1] = SERVER_NAME_FETCHER; default_request_array[(i++ << 1) + 1] = REMOTE_ADDR_FETCHER; default_request_array[(i++ << 1) + 1] = QUERY_STRING_FETCHER; default_request_array[(i++ << 1) + 1] = SCHEME_FETCHER; default_request_array[(i++ << 1) + 1] = REQUEST_METHOD_FETCHER; default_request_array[(i++ << 1) + 1] = CONTENT_TYPE_FETCHER; default_request_array[(i++ << 1) + 1] = CHARACTER_ENCODING_FETCHER; } } protected int validLen; protected long r; protected Object[] array; protected NginxHandler handler; protected NginxHttpServerChannel channel; protected byte[] hijackTag; protected int phase = -1; protected int evalCount = 0; protected int nativeCount = -1; protected volatile boolean released = false; protected List<java.util.AbstractMap.SimpleEntry<Object, ChannelListener<Object>>> listeners; public final static LazyRequestMap EMPTY_MAP = new LazyRequestMap(null, 0, null, new Object[0]); private final static ChannelListener<NginxRequest> requestListener = NginxJavaRequest.requestListener; public LazyRequestMap(NginxHandler handler, long r, byte[] hijackTag, Object[] array) { this.handler = handler; this.r = r; this.array = array; this.hijackTag = hijackTag; if (r != 0) { NginxClojureRT.addListener(r, requestListener, this, 1); } validLen = array.length; } @SuppressWarnings("unchecked") public LazyRequestMap(NginxHandler handler, long r) { //TODO: SSL_CLIENT_CERT this(handler, r, new byte[]{0}, new Object[default_request_array.length]); System.arraycopy(default_request_array, 0, array, 0, default_request_array.length); if (NginxClojureRT.log.isDebugEnabled()) { valAt(URI); } } private LazyRequestMap(LazyRequestMap or, Object[] a) { this.handler = or.handler; this.r = or.r; this.listeners = or.listeners; this.array = a; this.hijackTag = or.hijackTag; if (r != 0) { NginxClojureRT.addListener(r, requestListener, this, 1); } validLen = a.length; } public void reset(long r, NginxClojureHandler handler) { this.r = r; this.released = false; this.evalCount = 0; this.hijackTag[0] = 0; phase = -1; this.handler = handler; if (r != 0) { NginxClojureRT.addListener(r, requestListener, this, 1); nativeCount = -1; } } public void prefetchAll() { int len = count(); for (int i = 0; i < len; i++) { element(i*2); } } protected int index(Object key) { for (int i = 0; i < validLen; i+=2){ if (key == array[i]) { return i; } } return -1; } @Override public Iterator iterator() { return new Iterator<MapEntry>() { int i = 0; @Override public boolean hasNext() { return i < validLen - 2; } @Override public MapEntry next() { return new MapEntry(array[i++], array[i++]); } @Override public void remove() { throw Util.runtimeException("remove not supported"); } }; } @Override public IMapEntry entryAt(Object key) { Object v = valAt(key); if (v == null) { return null; } return new MapEntry(key, v); } @Override public int count() { return validLen >> 1; } @Override public IPersistentCollection cons(Object o) { if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry) o; return assoc(e.getKey(), e.getValue()); } else if (o instanceof IPersistentVector) { IPersistentVector v = (IPersistentVector) o; if (v.count() != 2) throw new IllegalArgumentException( "Vector arg to map conj must be a pair"); return assoc(v.nth(0), v.nth(1)); } IPersistentMap ret = this; for (ISeq es = RT.seq(o); es != null; es = es.next()) { Map.Entry e = (Map.Entry) es.first(); ret = ret.assoc(e.getKey(), e.getValue()); } return ret; } @Override public IPersistentCollection empty() { return EMPTY_MAP; } @Override public boolean equiv(Object o) { return o == this; } static class ArrayMapSeq extends ASeq implements Counted{ final int i; final LazyRequestMap reqMap; ArrayMapSeq(LazyRequestMap reqMap, int i){ this.reqMap = reqMap; this.i = i; } public ArrayMapSeq(IPersistentMap meta, LazyRequestMap reqMap, int i){ super(meta); this.reqMap = reqMap; this.i = i; } public Object first(){ return new MapEntry(reqMap.array[i],reqMap.element(i)); } public ISeq next(){ if(i + 2 < reqMap.validLen) return new ArrayMapSeq(reqMap, i + 2); return null; } public int count(){ return (reqMap.validLen - i) >> 1; } public Obj withMeta(IPersistentMap meta){ return new ArrayMapSeq(meta, reqMap, i); } } @Override public ISeq seq() { return new ArrayMapSeq(this, 0); } protected Object element(int i) { Object o = array[i+1]; if (o instanceof RequestVarFetcher) { if (released) { return null; } if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { throw new IllegalAccessError("fetching lazy value of " + array[i-1] + " in LazyRequestMap can only be called in main thread, please pre-access it in main thread OR call LazyRequestMap.prefetchAll() first in main thread"); } RequestVarFetcher rf = (RequestVarFetcher) o; array[i+1] = null; Object rt = rf.fetch(r, DEFAULT_ENCODING); array[i+1] = rt; // System.out.println("LazyRequestMap, key=" + rt); return rt; } // System.out.println("LazyRequestMap, key=" + array[i] + ", val=" + o); return o; } @Override public Object valAt(Object key) { int i = index(key); if (i == -1) { return null; } return element(i); } @Override public Object valAt(Object key, Object notFound) { Object val = valAt(key); return val == null ? notFound : val; } @Override public IPersistentMap assoc(Object key, Object val) { int i = index(key); if (i != -1) { array[i+1] = val; return this; } if (validLen < array.length) { array[validLen++] = key; array[validLen++] = val; return this; }else { Object[] newArray = new Object[array.length + 8]; System.arraycopy(array, 0, newArray, 0, array.length); newArray[array.length] = key; newArray[array.length+1] = val; validLen += 2; this.array = newArray; return this; } } @Override public IPersistentMap assocEx(Object key, Object val) { Object old = valAt(key); if (old != null) { throw Util.runtimeException("Key already present"); } return assoc(key, val); } @Override public IPersistentMap without(Object key) { int i = index(key); if (i == -1) { return this; }else { System.arraycopy(array, i + 2, array, i, validLen - i - 2); validLen -= 2; Stack.fillNull(array, validLen, array.length - validLen); return this; } } public long nativeRequest() { return r; } @Override public boolean containsKey(Object key) { return index(key) != -1; } @Override public Object invoke(Object key) { return valAt(key); } @Override public Object invoke(Object key, Object notFound) { return valAt(key, notFound); } @Override public NginxHandler handler() { return handler; } @Override public NginxHttpServerChannel channel() { if (hijackTag == null || hijackTag[0] == 0) { NginxClojureRT.UNSAFE.throwException(new IllegalAccessException("not hijacked!")); } return channel; } @Override public boolean isHijacked() { return hijackTag != null && hijackTag[0] == 1; } @Override public boolean isReleased() { return released; } @Override public int phase() { return phase; } public boolean isWebSocket() { return (Boolean) valAt(WEBSOCKET); } protected LazyRequestMap phase(int phase) { this.phase = phase; return this; } @Override public String toString() { return String.format("request {id : %d, uri: %s}", r, element(0)); } @Override public <T> void addListener(final T data, final ChannelListener<T> listener) { if (listeners == null) { listeners = new ArrayList<java.util.AbstractMap.SimpleEntry<Object, ChannelListener<Object>>>(1); } listeners.add(new java.util.AbstractMap.SimpleEntry<Object, ChannelListener<Object>>(data, (ChannelListener)listener)); // if (isWebSocket()) { //handshake was complete so we need call onConnect manually //handshake was complete so we need call onConnect manually Runnable action = new Coroutine.FinishAwaredRunnable() { @Override public void run() { try { listener.onConnect(NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK, data); } catch (Throwable e) { NginxClojureRT.log.error(String.format("#%d: onConnect Error!", r), e); } } @Override public void onFinished(Coroutine c) { RequestRawMessageAdapter.pooledCoroutines.add(c); } }; if (NginxClojureRT.coroutineEnabled && Coroutine.getActiveCoroutine() == null) { Coroutine coroutine = RequestRawMessageAdapter.pooledCoroutines.poll(); if (coroutine == null) { coroutine = new Coroutine(action); }else { coroutine.reset(action); } coroutine.resume(); }else if (NginxClojureRT.workers == null) { action.run(); }else { NginxClojureRT.workerExecutorService.submit(new RequestOrderedRunnable("onConnect2", action, LazyRequestMap.this)); } // } } @Override public void tagReleased() { this.released = true; this.channel = null; System.arraycopy(default_request_array, 0, array, 0, default_request_array.length); validLen = default_request_array.length; if (array.length > validLen) { Stack.fillNull(array, validLen, array.length - validLen); } if (listeners != null) { listeners.clear(); } ((NginxClojureHandler)handler).returnToRequestPool(this); } @Override public List<java.util.AbstractMap.SimpleEntry<Object, ChannelListener<Object>>> listeners() { return listeners; } @Override public String uri() { return (String) valAt(URI); } @Override public NginxHttpServerChannel hijack(boolean ignoreFilter) { return handler.hijack(this, ignoreFilter); } public long nativeCount() { return nativeCount; } public long nativeCount(long c) { int old = nativeCount; nativeCount = (int)c; return old; } public int refreshNativeCount() { return nativeCount = (int)NginxClojureRT.ngx_http_clojure_mem_inc_req_count(r, 0); } @Override public int getAndIncEvalCount() { return evalCount++; } }