/** * Check if the if-modified-since condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfModifiedSince( final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { try { final String reqModified = request.getHeader(Header.IfModifiedSince); if (reqModified != null) { // optimization - assume the String value sent in the // client's If-Modified-Since header is the same as what // was originally sent if (reqModified.equals(entry.lastModifiedHeader)) { return HttpStatus.NOT_MODIFIED_304; } long headerValue = convertToLong(reqModified); if (headerValue != -1) { long lastModified = entry.lastModified; // If an If-None-Match header has been specified, // If-Modified-Since is ignored. if ((request.getHeader(Header.IfNoneMatch) == null) && (lastModified - headerValue <= 1000)) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.NOT_MODIFIED_304; } } } } catch (IllegalArgumentException illegalArgument) { notifyProbesError(this, illegalArgument); } return null; }
private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) { if (request.hasHeaders()) { final FluentCaseInsensitiveStringsMap map = request.getHeaders(); for (final Map.Entry<String, List<String>> entry : map.entrySet()) { final String headerName = entry.getKey(); final List<String> headerValues = entry.getValue(); if (isNonEmpty(headerValues)) { for (int i = 0, len = headerValues.size(); i < len; i++) { requestPacket.addHeader(headerName, headerValues.get(i)); } } } } final MimeHeaders headers = requestPacket.getHeaders(); if (!headers.contains(Header.Connection)) { // final boolean canCache = context.provider.clientConfig.getAllowPoolingConnection(); requestPacket.addHeader(Header.Connection, /*(canCache ? */ "keep-alive" /*: "close")*/); } if (!headers.contains(Header.Accept)) { requestPacket.addHeader(Header.Accept, "*/*"); } if (!headers.contains(Header.UserAgent)) { requestPacket.addHeader(Header.UserAgent, config.getUserAgent()); } }
private static void recycleRequestResponsePackets( final Connection c, final HttpResponsePacket response) { if (!Utils.isSpdyConnection(c)) { HttpRequestPacket request = response.getRequest(); request.setExpectContent(false); response.recycle(); request.recycle(); } }
private HttpRequestPacket createRequest() { HttpRequestPacket.Builder builder = HttpRequestPacket.builder(); builder.method("POST"); builder.protocol("HTTP/1.1"); builder.uri("/echo"); builder.chunked(true); HttpRequestPacket packet = builder.build(); packet.addHeader(Header.Host, HOST_HEADER_VALUE); return packet; }
/** * Notify registered {@link FileCacheProbe}s about the "entry missed" event. * * @param fileCache the <tt>FileCache</tt> event occurred on. * @param request HTTP request. */ protected static void notifyProbesEntryMissed( final FileCache fileCache, final HttpRequestPacket request) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null && probes.length > 0) { for (FileCacheProbe probe : probes) { probe.onEntryMissedEvent( fileCache, request.getHeader(Header.Host), request.getRequestURI()); } } }
private static void addHostHeader( final Request request, final URI uri, final HttpRequestPacket requestPacket) { String host = request.getVirtualHost(); if (host != null) { requestPacket.addHeader(Header.Host, host); } else { if (uri.getPort() == -1) { requestPacket.addHeader(Header.Host, uri.getHost()); } else { requestPacket.addHeader(Header.Host, uri.getHost() + ':' + uri.getPort()); } } }
private static void addQueryString(final Request request, final HttpRequestPacket requestPacket) { final FluentStringsMap map = request.getQueryParams(); if (isNonEmpty(map)) { StringBuilder sb = new StringBuilder(128); for (final Map.Entry<String, List<String>> entry : map.entrySet()) { final String name = entry.getKey(); final List<String> values = entry.getValue(); if (isNonEmpty(values)) { try { for (int i = 0, len = values.size(); i < len; i++) { final String value = values.get(i); if (isNonEmpty(value)) { sb.append(URLEncoder.encode(name, "UTF-8")) .append('=') .append(URLEncoder.encode(values.get(i), "UTF-8")) .append('&'); } else { sb.append(URLEncoder.encode(name, "UTF-8")).append('&'); } } } catch (UnsupportedEncodingException ignored) { } } } sb.setLength(sb.length() - 1); String queryString = sb.toString(); requestPacket.setQueryString(queryString); } }
/** * Check if the if-match condition is satisfied. * * @param request The servlet request we are processing * @param entry the FileCacheEntry to validate * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfMatch(final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { String headerValue = request.getHeader(Header.IfMatch); if (headerValue != null) { if (headerValue.indexOf('*') == -1) { String eTag = entry.Etag; StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ","); boolean conditionSatisfied = false; while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { String currentToken = commaTokenizer.nextToken(); if (currentToken.trim().equals(eTag)) { conditionSatisfied = true; } } // If none of the given ETags match, 412 Precondition failed is // sent back if (!conditionSatisfied) { return HttpStatus.PRECONDITION_FAILED_412; } } } return null; }
/** * Check if the if-unmodified-since condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfUnmodifiedSince( final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { try { long lastModified = entry.lastModified; String h = request.getHeader(Header.IfUnmodifiedSince); if (h != null) { // optimization - assume the String value sent in the // client's If-Unmodified-Since header is the same as what // was originally sent if (h.equals(entry.lastModifiedHeader)) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.PRECONDITION_FAILED_412; } long headerValue = convertToLong(h); if (headerValue != -1) { if (headerValue - lastModified <= 1000) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.PRECONDITION_FAILED_412; } } } } catch (IllegalArgumentException illegalArgument) { notifyProbesError(this, illegalArgument); } return null; }
/** * Returns {@link FileCacheEntry}. If {@link FileCacheEntry} has been found - this method also * sets correspondent {@link HttpResponsePacket} status code and reason phrase. */ public FileCacheEntry get(final HttpRequestPacket request) { // It should be faster than calculating the key hash code if (cacheSize.get() == 0) return null; final LazyFileCacheKey key = LazyFileCacheKey.create(request); final FileCacheEntry entry = fileCacheMap.get(key); key.recycle(); try { if (entry != null && entry != NULL_CACHE_ENTRY) { // determine if we need to send the cache entry bytes // to the user-agent final HttpStatus httpStatus = checkIfHeaders(entry, request); final boolean flushBody = (httpStatus == null); if (flushBody && entry.type == CacheType.TIMESTAMP) { return null; // this will cause control to be passed to the static handler } request.getResponse().setStatus(httpStatus != null ? httpStatus : HttpStatus.OK_200); notifyProbesEntryHit(this, entry); return entry; } notifyProbesEntryMissed(this, request); } catch (Exception e) { notifyProbesError(this, e); // If an unexpected exception occurs, try to serve the page // as if it wasn't in a cache. LOGGER.log( Level.WARNING, LogMessages.WARNING_GRIZZLY_HTTP_SERVER_FILECACHE_GENERAL_ERROR(), e); } return null; }
/** * Check if the if-none-match condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfNoneMatch(final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { String headerValue = request.getHeader(Header.IfNoneMatch); if (headerValue != null) { String eTag = entry.Etag; boolean conditionSatisfied = false; if (!headerValue.equals("*")) { StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ","); while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { String currentToken = commaTokenizer.nextToken(); if (currentToken.trim().equals(eTag)) { conditionSatisfied = true; } } } else { conditionSatisfied = true; } if (conditionSatisfied) { // For GET and HEAD, we should respond with // 304 Not Modified. // For every other method, 412 Precondition Failed is sent // back. final Method method = request.getMethod(); if (Method.GET.equals(method) || Method.HEAD.equals(method)) { return HttpStatus.NOT_MODIFIED_304; } else { return HttpStatus.PRECONDITION_FAILED_412; } } } return null; }
private void addCookies(final Request request, final HttpRequestPacket requestPacket) { final Collection<Cookie> cookies = request.getCookies(); if (isNonEmpty(cookies)) { StringBuilder sb = new StringBuilder(128); org.glassfish.grizzly.http.Cookie[] gCookies = new org.glassfish.grizzly.http.Cookie[cookies.size()]; convertCookies(cookies, gCookies); CookieSerializerUtils.serializeClientCookies( sb, false, config.isRfc6265CookieEncoding(), gCookies); requestPacket.addHeader(Header.Cookie, sb.toString()); } }
@SuppressWarnings({"unchecked"}) @Override public NextAction handleConnect(FilterChainContext ctx) throws IOException { System.out.println("\nClient connected!\n"); HttpRequestPacket request = createRequest(); System.out.println("Writing request:\n"); System.out.println(request.toString()); ctx.write(request); // write the request // for each of the content parts in CONTENT, wrap in a Buffer, // create the HttpContent to wrap the buffer and write the // content. MemoryManager mm = ctx.getMemoryManager(); for (int i = 0, len = CONTENT.length; i < len; i++) { HttpContent.Builder contentBuilder = request.httpContentBuilder(); Buffer b = Buffers.wrap(mm, CONTENT[i]); contentBuilder.content(b); HttpContent content = contentBuilder.build(); System.out.printf("(Client writing: %s)\n", b.toStringContent()); ctx.write(content); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } // since the request created by createRequest() is chunked, // we need to write the trailer to signify the end of the // POST data ctx.write(request.httpTrailerBuilder().build()); System.out.println("\n"); return ctx.getStopAction(); // discontinue filter chain execution }
@SuppressWarnings("unchecked") public boolean sendRequest( final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { boolean isWriteComplete = true; if (Utils.requestHasEntityBody(request)) { final HttpTxContext context = HttpTxContext.get(ctx); BodyHandler handler = bodyHandlerFactory.getBodyHandler(request); if (requestPacket.getHeaders().contains(Header.Expect) && requestPacket.getHeaders().getValue(1).equalsIgnoreCase("100-Continue")) { // We have to set the content-length now as the headers will be flushed // before the FileBodyHandler is invoked. If we don't do it here, and // the user didn't explicitly set the length, then the transfer-encoding // will be chunked and zero-copy file transfer will not occur. final File f = request.getFile(); if (f != null) { requestPacket.setContentLengthLong(f.length()); } handler = new ExpectHandler(handler); } context.setBodyHandler(handler); if (logger.isDebugEnabled()) { logger.debug("REQUEST: {}", requestPacket); } isWriteComplete = handler.doHandle(ctx, request, requestPacket); } else { HttpContent content = HttpContent.builder(requestPacket).last(true).build(); if (logger.isDebugEnabled()) { logger.debug("REQUEST: {}", requestPacket); } ctx.write(content, ctx.getTransportContext().getCompletionHandler()); } return isWriteComplete; }
/** * Method invoked when a first massage (Assumed to be HTTP) is received. Normally this would be * HTTP CONNECT and this method processes it and opens a connection to the destination (the server * that the client wants to access). * * <p>This method can be overridden to provide a test-specific handling of the CONNECT method. */ protected NextAction handleConnect(FilterChainContext ctx, HttpContent content) { System.out.println("Handle CONNECT start . . ."); HttpHeader httpHeader = content.getHttpHeader(); HttpRequestPacket requestPacket = (HttpRequestPacket) httpHeader.getHttpHeader(); if (!requestPacket.getMethod().matchesMethod("CONNECT")) { System.out.println("Received method is not CONNECT"); writeHttpResponse(ctx, 400); return ctx.getStopAction(); } String destinationUri = requestPacket.getRequestURI(); // We expect URI in form host:port, this is not flexible, but we use it only to test our clients int colonIdx = destinationUri.indexOf(':'); if (colonIdx == -1) { System.out.println("Destination URI not in host:port format: " + destinationUri); writeHttpResponse(ctx, 400); return ctx.getStopAction(); } String hostName = destinationUri.substring(0, colonIdx); String portStr = destinationUri.substring(colonIdx + 1); int port; try { port = Integer.parseInt(portStr); } catch (Throwable t) { System.out.println("Could not parse destination port: " + portStr); writeHttpResponse(ctx, 400); return ctx.getStopAction(); } try { Socket tunnelSocket = new Socket(hostName, port); Connection grizzlyConnection = ctx.getConnection(); tunnelSockets.set(grizzlyConnection, tunnelSocket); TunnelSocketReader tunnelSocketReader = new TunnelSocketReader(tunnelSocket, grizzlyConnection); executorService.submit(tunnelSocketReader::read); } catch (IOException e) { writeHttpResponse(ctx, 400); return ctx.getStopAction(); } // Grizzly does not like CONNECT method and sets "keep alive" to false, if it is present // This hacks Grizzly, so it will keep the connection open HttpRequestPacket request = getHttpRequest(ctx); request.getResponse().getProcessingState().setKeepAlive(true); request.getResponse().setContentLength(0); request.setMethod("GET"); // end of hack writeHttpResponse(ctx, 200); System.out.println("Connection to proxy established."); return ctx.getStopAction(); }
/** Parse host. */ static void parseHost( final DataChunk hostDC, final DataChunk serverNameDC, final HttpRequestPacket request) { if (hostDC == null) { // HTTP/1.0 // Default is what the socket tells us. Overriden if a host is // found/parsed request.setServerPort(request.getLocalPort()); serverNameDC.setString(request.getLocalName()); return; } final BufferChunk valueBC = hostDC.getBufferChunk(); final int valueS = valueBC.getStart(); final int valueL = valueBC.getEnd() - valueS; int colonPos = -1; final Buffer valueB = valueBC.getBuffer(); final boolean ipv6 = (valueB.get(valueS) == '['); boolean bracketClosed = false; for (int i = 0; i < valueL; i++) { final byte b = valueB.get(i + valueS); if (b == ']') { bracketClosed = true; } else if (b == ':') { if (!ipv6 || bracketClosed) { colonPos = i; break; } } } if (colonPos < 0) { if (!request.isSecure()) { // 80 - Default HTTTP port request.setServerPort(80); } else { // 443 - Default HTTPS port request.setServerPort(443); } serverNameDC.setBuffer(valueB, valueS, valueS + valueL); } else { serverNameDC.setBuffer(valueB, valueS, valueS + colonPos); int port = 0; int mult = 1; for (int i = valueL - 1; i > colonPos; i--) { int charValue = DEC[(int) valueB.get(i + valueS)]; if (charValue == -1) { // Invalid character throw new IllegalStateException( String.format( "Host header %s contained a non-decimal value in the port definition.", hostDC.toString())); } port = port + (charValue * mult); mult = 10 * mult; } request.setServerPort(port); } }
/** Add a resource to the cache. */ protected CacheResult add( final HttpRequestPacket request, final File cacheFile, final long lastModified) { final String requestURI = request.getRequestURI(); if (requestURI == null) { return CacheResult.FAILED; } final String host = request.getHeader(Header.Host); final FileCacheKey key = new FileCacheKey(host, requestURI); if (fileCacheMap.putIfAbsent(key, NULL_CACHE_ENTRY) != null) { key.recycle(); return CacheResult.FAILED_ENTRY_EXISTS; } final int size = cacheSize.incrementAndGet(); // cache is full. if (size > getMaxCacheEntries()) { cacheSize.decrementAndGet(); fileCacheMap.remove(key); key.recycle(); return CacheResult.FAILED_CACHE_FULL; } final HttpResponsePacket response = request.getResponse(); final MimeHeaders headers = response.getHeaders(); final String contentType = response.getContentType(); final FileCacheEntry entry; if (cacheFile != null) { // If we have a file - try to create File-aware cache resource entry = createEntry(cacheFile); entry.setCanBeCompressed(canBeCompressed(cacheFile, contentType)); } else { entry = new FileCacheEntry(this); entry.type = CacheType.TIMESTAMP; } entry.key = key; entry.requestURI = requestURI; entry.lastModified = lastModified; entry.contentType = ContentType.newContentType(contentType); entry.xPoweredBy = headers.getHeader(Header.XPoweredBy); entry.date = headers.getHeader(Header.Date); entry.lastModifiedHeader = headers.getHeader(Header.LastModified); entry.host = host; entry.Etag = headers.getHeader(Header.ETag); entry.server = headers.getHeader(Header.Server); fileCacheMap.put(key, entry); notifyProbesEntryAdded(this, entry); final int secondsMaxAgeLocal = getSecondsMaxAge(); if (secondsMaxAgeLocal > 0) { delayQueue.add(entry, secondsMaxAgeLocal, TimeUnit.SECONDS); } return ((entry.type == CacheType.TIMESTAMP) ? CacheResult.OK_CACHED_TIMESTAMP : CacheResult.OK_CACHED); }
private boolean sendAsGrizzlyRequest( final RequestInfoHolder requestInfoHolder, final FilterChainContext ctx) throws IOException { HttpTxContext httpTxContext = requestInfoHolder.getHttpTxContext(); if (httpTxContext == null) { httpTxContext = HttpTxContext.create(requestInfoHolder); } if (checkProxyAuthFailure(ctx, httpTxContext)) { return true; } final Request request = httpTxContext.getRequest(); final URI uri = request.isUseRawUrl() ? request.getRawURI() : request.getURI(); boolean secure = Utils.isSecure(uri); // If the request is secure, check to see if an error occurred during // the handshake. We have to do this here, as the error would occur // out of the scope of a HttpTxContext so there would be // no good way to communicate the problem to the caller. if (secure && checkHandshakeError(ctx, httpTxContext)) { return true; } if (isUpgradeRequest(httpTxContext.getHandler()) && isWSRequest(httpTxContext.getRequestUrl())) { httpTxContext.setWSRequest(true); convertToUpgradeRequest(httpTxContext); } HttpRequestPacket requestPacket = requestCache.poll(); if (requestPacket == null) { requestPacket = new HttpRequestPacketImpl(); } requestPacket.setMethod(request.getMethod()); requestPacket.setProtocol(Protocol.HTTP_1_1); // Special handling for CONNECT. if (Method.CONNECT.matchesMethod(request.getMethod())) { final int port = uri.getPort(); requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); } else { requestPacket.setRequestURI(uri.getPath()); } if (Utils.requestHasEntityBody(request)) { final long contentLength = request.getContentLength(); if (contentLength >= 0) { requestPacket.setContentLengthLong(contentLength); requestPacket.setChunked(false); } else { requestPacket.setChunked(true); } } if (httpTxContext.isWSRequest()) { try { final URI wsURI = new URI(httpTxContext.getWsRequestURI()); httpTxContext.setProtocolHandler(Version.RFC6455.createHandler(true)); httpTxContext.setHandshake(httpTxContext.getProtocolHandler().createHandShake(wsURI)); requestPacket = (HttpRequestPacket) httpTxContext.getHandshake().composeHeaders().getHttpHeader(); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid WS URI: " + httpTxContext.getWsRequestURI()); } } requestPacket.setSecure(secure); addQueryString(request, requestPacket); addHostHeader(request, uri, requestPacket); addGeneralHeaders(request, requestPacket); addCookies(request, requestPacket); initTransferCompletionHandler(request, httpTxContext.getHandler()); final HttpRequestPacket requestPacketLocal = requestPacket; FilterChainContext sendingCtx = ctx; if (secure) { // Check to see if the ProtocolNegotiator has given // us a different FilterChain to use. If so, we need // use a different FilterChainContext when invoking sendRequest(). sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx); } final Connection c = ctx.getConnection(); if (!Utils.isSpdyConnection(c)) { HttpContext.newInstance(ctx, c, c, c); } else { SpdySession session = SpdySession.get(c); final Lock lock = session.getNewClientStreamLock(); try { lock.lock(); SpdyStream stream = session.openStream( requestPacketLocal, session.getNextLocalStreamId(), 0, 0, 0, false, !requestPacketLocal.isExpectContent()); HttpContext.newInstance(ctx, stream, stream, stream); } finally { lock.unlock(); } } HttpTxContext.set(ctx, httpTxContext); return sendRequest(sendingCtx, request, requestPacketLocal); }
@Override public void recycle() { super.recycle(); processingState.recycle(); requestCache.add(this); }
/* (non-Javadoc) * @see org.glassfish.grizzly.websockets.WebSocketApplication#isApplicationRequest(org.glassfish.grizzly.http.HttpRequestPacket) */ @Override public boolean isApplicationRequest(HttpRequestPacket request) { LOGGER.debug("isApplicationRequest: {}", request); return request.getRequestURI().toString().endsWith("/logviewer/websocket"); }