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 committed Apr 10, 2024
1 parent 128b967 commit f408a8a
Show file tree
Hide file tree
Showing 7 changed files with 214 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
89 changes: 89 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,89 @@
/*
* 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, because of newline the end of headers string
long requestSize = 2;
if (exchange.getRequestContentLength() != -1) {
requestSize += exchange.getRequestContentLength();
}
requestSize += calculateRequestLineSize(exchange);
requestSize += exchange.getRequestHeaders().getHeadersBytes();
// add 2 bytes for CRLF, and 2 bytes for ": " between header name and value
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, because of CRLF, and 2 spaces in
long size = 4;
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.HeaderValues;
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 responseSize to 2, because of newline the end of headers string
long responseSize = 2;
responseSize += exchange.getResponseBytesSent();
responseSize += calculateStatusLineSize(exchange);
responseSize += exchange.getResponseHeaders().getHeadersBytes();
// add 2 bytes for CR and LF, and 2 bytes for ": " between header name and value
responseSize += exchange.getResponseHeaders().size() * 4L;
return Long.toString(responseSize + responseSize);
}

// Status Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
private long calculateStatusLineSize(HttpServerExchange exchange) {
// initalize size to 7, because of 3 digit status code, CRLF and two spaces in the status line
long size = 7;
size += exchange.getProtocol().length();
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
16 changes: 16 additions & 0 deletions core/src/main/java/io/undertow/util/HeaderMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,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().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 f408a8a

Please sign in to comment.