Skip to content

Commit

Permalink
[UNDERTOW-2372] Add request size and response size exchange attribute…
Browse files Browse the repository at this point in the history
…s(sizes include headers).
  • Loading branch information
xjusko authored and fl4via committed Oct 21, 2024
1 parent 4f308ec commit 840f9dc
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public static ExchangeAttribute threadName() {
return ThreadNameAttribute.INSTANCE;
}

public static ExchangeAttribute requestSize() {
return RequestSizeAttribute.INSTANCE;
}

public static ExchangeAttribute responseSize() {
return ResponseSizeAttribute.INSTANCE;
}

public static ExchangeAttribute constant(String value) {
return new ConstantExchangeAttribute(value);
}
Expand Down
99 changes: 99 additions & 0 deletions core/src/main/java/io/undertow/attribute/RequestSizeAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.undertow.attribute;

import io.undertow.server.HttpServerExchange;

/**
* Size of request in bytes, including headers, cannot be zero
*
* @author Marek Jusko
*/

public class RequestSizeAttribute implements ExchangeAttribute{

public static final String REQUEST_SIZE_SHORT = "%E";
public static final String REQUEST_SIZE = "%{REQUEST_SIZE}";
public static final ExchangeAttribute INSTANCE = new RequestSizeAttribute();

@Override
public String readAttribute(HttpServerExchange exchange) {
// Initialize requestSize to 2 bytes for the CRLF at the end of headers string
long requestSize = 2;

// Add the request content length if it is specified
if (exchange.getRequestContentLength() != -1) {
requestSize += exchange.getRequestContentLength();
}
// Add the size of the request line
requestSize += calculateRequestLineSize(exchange);

// Add the size of all headers
requestSize += exchange.getRequestHeaders().getHeadersBytes();

// Add 4 bytes per header for ": " and CRLF
requestSize += exchange.getRequestHeaders().size() * 4L;

return Long.toString(requestSize);
}

// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
private long calculateRequestLineSize(HttpServerExchange exchange) {
// Initialize size to 4 bytes for the 2 spaces and CRLF in the request line
long size = 4; // 2 spaces + CRLF

// Add the length of the protocol, request method, and request path
size += exchange.getProtocol().length();
size += exchange.getRequestMethod().length();
size += exchange.getRequestPath().length();

return size;
}

@Override
public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
throw new ReadOnlyAttributeException("Size of request, including headers", newValue);
}

@Override
public String toString() {
return REQUEST_SIZE;
}

public static final class Builder implements ExchangeAttributeBuilder {

@Override
public String name() {
return "Request size";
}

@Override
public ExchangeAttribute build(String token) {
if (token.equals(REQUEST_SIZE) || token.equals(REQUEST_SIZE_SHORT)) {
return RequestSizeAttribute.INSTANCE;
}
return null;
}

@Override
public int priority() {
return 0;
}
}
}
103 changes: 103 additions & 0 deletions core/src/main/java/io/undertow/attribute/ResponseSizeAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.undertow.attribute;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.StatusCodes;

/**
* Size of response in bytes, including headers
*
* @author Marek Jusko
*/

public class ResponseSizeAttribute implements ExchangeAttribute{

public static final String RESPONSE_SIZE_SHORT = "%O";
public static final String RESPONSE_SIZE = "%{RESPONSE_SIZE}";
public static final ExchangeAttribute INSTANCE = new ResponseSizeAttribute();

@Override
public String readAttribute(HttpServerExchange exchange) {
if (exchange.getResponseHeaders().size() == 0) {
return "0";
}
// Initialize requestSize to 2 bytes for the CRLF at the end of headers string
long responseSize = 2;

// Add the number of bytes sent in the response body
responseSize += exchange.getResponseBytesSent();

// Add the size of the status line
responseSize += calculateStatusLineSize(exchange);

// Add the size of the headers
responseSize += exchange.getResponseHeaders().getHeadersBytes();

// Add 4 bytes per header for ": " and CRLF
responseSize += exchange.getResponseHeaders().size() * 4L;

return Long.toString(responseSize);
}

// Status Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
private long calculateStatusLineSize(HttpServerExchange exchange) {
// Initialize size to 7 bytes for the spaces, CRLF, and 3-digit status code
long size = 7; // 3 for status code + 2 for spaces + 2 for CRLF

// Add the length of the HTTP version
size += exchange.getProtocol().length();

// Add the length of the status message
size += StatusCodes.getReason(exchange.getStatusCode()).length();

return size;
}

@Override
public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
throw new ReadOnlyAttributeException("Size of response, including headers", newValue);
}

@Override
public String toString() {
return RESPONSE_SIZE;
}

public static final class Builder implements ExchangeAttributeBuilder {

@Override
public String name() {
return "Response size";
}

@Override
public ExchangeAttribute build(String token) {
if (token.equals(RESPONSE_SIZE) || token.equals(RESPONSE_SIZE_SHORT)) {
return ResponseSizeAttribute.INSTANCE;
}
return null;
}

@Override
public int priority() {
return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
* <li><b>%D</b> - Time taken to process the request, in millis
* <li><b>%T</b> - Time taken to process the request, in seconds
* <li><b>%I</b> - current Request thread name (can compare later with stacktraces)
* <li><b>%E</b> - Size of request in bytes, including headers, cannot be zero
* <li><b>%O</b> - Size of response in bytes, including headers
* </ul>
* <p>In addition, the caller can specify one of the following aliases for
* commonly utilized patterns:</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
import io.undertow.attribute.RequestMethodAttribute;
import io.undertow.attribute.RequestProtocolAttribute;
import io.undertow.attribute.RequestSchemeAttribute;
import io.undertow.attribute.RequestSizeAttribute;
import io.undertow.attribute.RequestURLAttribute;
import io.undertow.attribute.ResponseCodeAttribute;
import io.undertow.attribute.ResponseHeaderAttribute;
import io.undertow.attribute.ResponseSizeAttribute;
import io.undertow.attribute.ResponseTimeAttribute;
import io.undertow.attribute.SecureExchangeAttribute;
import io.undertow.attribute.SubstituteEmptyWrapper;
Expand Down Expand Up @@ -249,6 +251,10 @@ protected ExchangeAttribute getLogElement(String token, PatternTokenizer tokeniz
}
} else if ("bytes".equals(token)) {
return new BytesSentAttribute(true);
} else if ("responseSize".equals(token)) {
return ResponseSizeAttribute.INSTANCE;
} else if ("requestSize".equals(token)) {
return RequestSizeAttribute.INSTANCE;
} else if ("cached".equals(token)) {
/* I don't know how to evaluate this! */
return new ConstantExchangeAttribute("-");
Expand Down
17 changes: 17 additions & 0 deletions core/src/main/java/io/undertow/util/HeaderMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static org.wildfly.common.Assert.checkNotNullParam;

import java.nio.charset.StandardCharsets;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -834,6 +835,22 @@ public boolean contains(String headerName) {
return false;
}

public long getHeadersBytes() {
long headersSize = 0;
long cookie = this.fastIterateNonEmpty();
while (cookie != -1L) {
HeaderValues header = this.fiCurrent(cookie);
headersSize += header.getHeaderName().length(); // Size of the header name
for (String value : header) {
headersSize += value.getBytes(StandardCharsets.UTF_8).length; // Size of each header value
}

// Get the next non-empty header cookie
cookie = this.fiNextNonEmpty(cookie);
}
return headersSize;
}

// compare

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ io.undertow.attribute.NullAttribute$Builder
io.undertow.attribute.StoredResponse$Builder
io.undertow.attribute.ResponseReasonPhraseAttribute$Builder
io.undertow.attribute.RemoteObfuscatedIPAttribute$Builder
io.undertow.attribute.RequestSizeAttribute$Builder
io.undertow.attribute.ResponseSizeAttribute$Builder

0 comments on commit 840f9dc

Please sign in to comment.