private void recycleSocket(final AsyncSocket socket, AsyncHttpRequest request) { if (socket == null) return; URI uri = request.getUri(); int port = getSchemePort(uri); String lookup = computeLookup(uri, port, request); // nothing here will block... synchronized (this) { HashSet<AsyncSocket> sockets = mSockets.get(lookup); if (sockets == null) { sockets = new HashSet<AsyncSocket>(); mSockets.put(lookup, sockets); } final HashSet<AsyncSocket> ss = sockets; sockets.add(socket); socket.setClosedCallback( new CompletedCallback() { @Override public void onCompleted(Exception ex) { synchronized (AsyncSocketMiddleware.this) { ss.remove(socket); } socket.setClosedCallback(null); } }); } }
private void idleSocket(final AsyncSocket socket) { // must listen for socket close, otherwise log will get spammed. socket.setEndCallback( new CompletedCallback() { @Override public void onCompleted(Exception ex) { socket.close(); } }); socket.setWriteableCallback(null); // should not get any data after this point... // if so, eat it and disconnect. socket.setDataCallback( new NullDataCallback() { @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { super.onDataAvailable(emitter, bb); bb.recycle(); socket.close(); } }); }
void setSocket(AsyncSocket exchange) { mSocket = exchange; if (mSocket == null) return; mWriter = mRequest.getBody(); if (mWriter != null) { mRequest.getHeaders().setContentType(mWriter.getContentType()); if (mWriter.length() != -1) { mRequest.getHeaders().setContentLength(mWriter.length()); mSink = mSocket; } else { mRequest.getHeaders().getHeaders().set("Transfer-Encoding", "Chunked"); mSink = new ChunkedOutputFilter(mSocket); } } else { mSink = mSocket; } String rs = mRequest.getRequestString(); com.koushikdutta.async.Util.writeAll( exchange, rs.getBytes(), new CompletedCallback() { @Override public void onCompleted(Exception ex) { if (mWriter != null) mWriter.write(mRequest, AsyncHttpResponseImpl.this); } }); LineEmitter liner = new LineEmitter(); exchange.setDataCallback(liner); liner.setLineCallback(mHeaderCallback); mSocket.setEndCallback(mReporter); mSocket.setClosedCallback( new CompletedCallback() { @Override public void onCompleted(Exception ex) { // TODO: do we care? throw if socket is still writing or something? } }); }
@Override public Cancellable getSocket(final AsyncHttpClientMiddleware.GetSocketData data) { final URI uri = data.request.getUri(); final int port = getSchemePort(data.request.getUri()); if (port == -1) { return null; } ConnectionInfo info = getConnectionInfo(uri.getScheme(), uri.getHost(), port); if (info.openCount >= maxConnectionCount) { // wait for a connection queue to free up SimpleCancellable queueCancel = new SimpleCancellable(); info.queue.add(data); return queueCancel; } info.openCount++; final String lookup = computeLookup(uri, port, data.request); data.state.putBoolean(getClass().getCanonicalName() + ".owned", true); synchronized (this) { final HashSet<AsyncSocket> sockets = mSockets.get(lookup); if (sockets != null) { for (final AsyncSocket socket : sockets) { if (socket.isOpen()) { sockets.remove(socket); socket.setClosedCallback(null); mClient .getServer() .post( new Runnable() { @Override public void run() { data.request.logd("Reusing keep-alive socket"); data.connectCallback.onConnectCompleted(null, socket); } }); // replace above code with immediate callback? // data.request.logd("Reusing keep-alive socket"); // data.connectCallback.onConnectCompleted(null, socket); // just a noop/dummy, as this can't actually be cancelled. return new SimpleCancellable(); } } } } if (!connectAllAddresses || proxyHost != null || data.request.getProxyHost() != null) { // just default to connecting to a single address data.request.logd("Connecting socket"); String unresolvedHost; int unresolvedPort; if (data.request.getProxyHost() != null) { unresolvedHost = data.request.getProxyHost(); unresolvedPort = data.request.getProxyPort(); // set the host and port explicitly for proxied connections data.request .getHeaders() .getHeaders() .setStatusLine(data.request.getProxyRequestLine().toString()); } else if (proxyHost != null) { unresolvedHost = proxyHost; unresolvedPort = proxyPort; // set the host and port explicitly for proxied connections data.request .getHeaders() .getHeaders() .setStatusLine(data.request.getProxyRequestLine().toString()); } else { unresolvedHost = uri.getHost(); unresolvedPort = port; } return mClient .getServer() .connectSocket( unresolvedHost, unresolvedPort, wrapCallback(data.connectCallback, uri, port)); } // try to connect to everything... data.request.logv("Resolving domain and connecting to all available addresses"); return mClient .getServer() .getAllByName(uri.getHost()) .then( new TransformFuture<AsyncSocket, InetAddress[]>() { Exception lastException; @Override protected void error(Exception e) { super.error(e); data.connectCallback.onConnectCompleted(e, null); } @Override protected void transform(final InetAddress[] result) throws Exception { Continuation keepTrying = new Continuation( new CompletedCallback() { @Override public void onCompleted(Exception ex) { // if it completed, that means that the connection failed if (lastException == null) lastException = new ConnectionFailedException( "Unable to connect to remote address"); setComplete(lastException); } }); for (final InetAddress address : result) { keepTrying.add( new ContinuationCallback() { @Override public void onContinue( Continuation continuation, final CompletedCallback next) throws Exception { mClient .getServer() .connectSocket( new InetSocketAddress(address, port), wrapCallback( new ConnectCallback() { @Override public void onConnectCompleted( Exception ex, AsyncSocket socket) { if (isDone()) { lastException = new Exception("internal error during connect"); next.onCompleted(null); return; } // try the next address if (ex != null) { lastException = ex; next.onCompleted(null); return; } // if the socket is no longer needed, just hang onto it... if (isDone() || isCancelled()) { data.request.logd( "Recycling extra socket leftover from cancelled operation"); idleSocket(socket); recycleSocket(socket, data.request); return; } if (setComplete(null, socket)) { data.connectCallback.onConnectCompleted(ex, socket); } } }, uri, port)); } }); } keepTrying.start(); } }); }
@Override public AsyncServer getServer() { return mSocket.getServer(); }
@Override public void onAccepted(final AsyncSocket socket) { AsyncHttpServerRequestImpl req = new AsyncHttpServerRequestImpl() { Pair match; String fullPath; String path; boolean responseComplete; boolean requestComplete; AsyncHttpServerResponseImpl res; boolean hasContinued; @Override protected AsyncHttpRequestBody onUnknownBody(RawHeaders headers) { return AsyncHttpServer.this.onUnknownBody(headers); } @Override protected void onHeadersReceived() { RawHeaders headers = getRawHeaders(); // should the negotiation of 100 continue be here, or in the request impl? // probably here, so AsyncResponse can negotiate a 100 continue. if (!hasContinued && "100-continue".equals(headers.get("Expect"))) { pause(); // System.out.println("continuing..."); Util.writeAll( mSocket, "HTTP/1.1 100 Continue\r\n\r\n".getBytes(), new CompletedCallback() { @Override public void onCompleted(Exception ex) { resume(); if (ex != null) { report(ex); return; } hasContinued = true; onHeadersReceived(); } }); return; } // System.out.println(headers.toHeaderString()); String statusLine = headers.getStatusLine(); String[] parts = statusLine.split(" "); fullPath = parts[1]; path = fullPath.split("\\?")[0]; method = parts[0]; synchronized (mActions) { ArrayList<Pair> pairs = mActions.get(method); if (pairs != null) { for (Pair p : pairs) { Matcher m = p.regex.matcher(path); if (m.matches()) { mMatcher = m; match = p; break; } } } } res = new AsyncHttpServerResponseImpl(socket, this) { @Override protected void onEnd() { super.onEnd(); mSocket.setEndCallback(null); responseComplete = true; // reuse the socket for a subsequent request. handleOnCompleted(); } }; onRequest(this, res); if (match == null) { res.responseCode(404); res.end(); return; } if (!getBody().readFullyOnRequest()) { match.callback.onRequest(this, res); } else if (requestComplete) { match.callback.onRequest(this, res); } } @Override public void onCompleted(Exception e) { // if the protocol was switched off http, ignore this request/response. if (res.getHeaders().getHeaders().getResponseCode() == 101) return; requestComplete = true; super.onCompleted(e); // no http pipelining, gc trashing if the socket dies // while the request is being sent and is paused or something mSocket.setDataCallback( new NullDataCallback() { @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { super.onDataAvailable(emitter, bb); mSocket.close(); } }); handleOnCompleted(); if (getBody().readFullyOnRequest()) { if (match != null) match.callback.onRequest(this, res); } } private void handleOnCompleted() { if (requestComplete && responseComplete) { if (HttpUtil.isKeepAlive(getHeaders().getHeaders())) { onAccepted(socket); } else { socket.close(); } } } @Override public String getPath() { return path; } @Override public Multimap getQuery() { String[] parts = fullPath.split("\\?", 2); if (parts.length < 2) return new Multimap(); return Multimap.parseQuery(parts[1]); } }; req.setSocket(socket); socket.resume(); }