Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UNDERTOW-2372] Add request size and response size exchange attribute… #1579

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if HTTP 2 protocol is in use? The calculations here expect HTTP 1.1 is being used.

// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if HTTP 2 protocol is in use? The calculations here expect HTTP 1.1 is being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, but AFAIR we cant account for, for instance, header compression? Also, even for H1 with compression this will be off?
@xjusko ^^ ?

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

Loading