java-topology/src/java.net.http/share/classes/jdk/internal/net/http/Http2TerminationCause.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

281 lines
10 KiB
Java

/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.net.http;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Objects;
import jdk.internal.net.http.common.Utils;
import jdk.internal.net.http.frame.ErrorFrame;
/**
* Termination cause for an {@linkplain Http2Connection HTTP/2 connection}
*/
public abstract sealed class Http2TerminationCause {
private String logMsg;
private String peerVisibleReason;
private final int closeCode;
private final Throwable originalCause;
private final IOException reportedCause;
private Http2TerminationCause(final int closeCode, final Throwable closeCause) {
this.closeCode = closeCode;
this.originalCause = closeCause;
if (closeCause != null) {
this.logMsg = closeCause.toString();
}
this.reportedCause = toReportedCause(this.originalCause, this.logMsg);
}
private Http2TerminationCause(final int closeCode, final String loggedAs) {
this.closeCode = closeCode;
this.originalCause = null;
this.logMsg = loggedAs;
this.reportedCause = toReportedCause(null, this.logMsg);
}
/**
* Returns the error code (specified for HTTP/2 ErrorFrame) that caused the
* connection termination.
*/
public final int getCloseCode() {
return this.closeCode;
}
/**
* Returns the {@link IOException} that is considered the cause of the connection termination.
* Even a {@linkplain #isAbnormalClose() normal} termination will have
* an {@code IOException} associated with it, so this method will always return a non-null instance.
*/
public final IOException getCloseCause() {
return this.reportedCause;
}
/**
* Returns {@code true} if the connection was terminated due to some exception. {@code false}
* otherwise.
* A normal connection termination (for example, the connection idle timing out locally)
* is not considered as an abnormal termination and this method returns {@code false} for
* such cases.
*/
public abstract boolean isAbnormalClose();
/**
* Returns the connection termination cause, represented as a string. Unlike the
* {@linkplain #getPeerVisibleReason() peer-visible reason}, this log message will not be
* sent across to the peer and it is thus allowed to include additional details that might
* help debugging a connection termination.
*/
public final String getLogMsg() {
return logMsg;
}
/**
* Returns the connection termination cause, represented as a string. This represents the
* "debugData" that is sent to the peer in a
* {@linkplain jdk.internal.net.http.frame.GoAwayFrame GOAWAY frame}.
*/
public final String getPeerVisibleReason() {
return this.peerVisibleReason;
}
/**
* Sets the connection termination cause, represented as a string, which will be sent
* to the peer in a {@linkplain jdk.internal.net.http.frame.GoAwayFrame GOAWAY frame}.
* Unlike the {@link #getLogMsg() log message},
* it is expected that this peer-visible reason will not contain anything that is not meant
* to be viewed by the peer.
*/
protected final void setPeerVisibleReason(final String reasonPhrase) {
this.peerVisibleReason = reasonPhrase;
}
/**
* Returns a connection termination cause that represents an
* {@linkplain #isAbnormalClose() abnormal} termination due to the given {@code cause}.
*
* @param cause the termination cause, cannot be null.
*/
public static Http2TerminationCause forException(final Throwable cause) {
Objects.requireNonNull(cause);
if (cause instanceof ProtocolException pe) {
return new ProtocolError(pe);
}
return new InternalError(cause);
}
/**
* Returns a connection termination cause that represents a
* {@linkplain #isAbnormalClose() normal} termination.
*/
public static Http2TerminationCause noErrorTermination() {
return NoError.INSTANCE;
}
/**
* Returns a connection termination cause that represents a
* {@linkplain #isAbnormalClose() normal} termination due to the connection
* being idle.
*/
public static Http2TerminationCause idleTimedOut() {
return NoError.IDLE_TIMED_OUT;
}
/**
* Returns a connection termination cause that represents an
* {@linkplain #isAbnormalClose() abnormal} termination due to the given {@code errorCode}.
* Although this method does no checks for the {@code errorCode}, it is expected to be one
* of the error codes specified by the HTTP/2 RFC for the ErrorFrame.
*
* @param errorCode the error code
* @param loggedAs optional log message to be associated with this termination cause
*/
public static Http2TerminationCause forH2Error(final int errorCode, final String loggedAs) {
if (errorCode == ErrorFrame.PROTOCOL_ERROR) {
return new ProtocolError(loggedAs);
} else if (errorCode == ErrorFrame.FLOW_CONTROL_ERROR) {
// we treat flow control error as a protocol error currently
return new ProtocolError(loggedAs, true);
}
return new H2StandardError(errorCode, loggedAs);
}
private static IOException toReportedCause(final Throwable original,
final String fallbackExceptionMsg) {
if (original == null) {
return fallbackExceptionMsg == null
? new IOException("connection terminated")
: new IOException(fallbackExceptionMsg);
} else if (original instanceof IOException ioe) {
return ioe;
} else {
return Utils.toIOException(original);
}
}
private static final class NoError extends Http2TerminationCause {
private static final IOException NO_ERROR_MARKER =
new IOException("HTTP/2 connection closed normally - no error");
private static final IOException NO_ERROR_IDLE_TIMED_OUT_MARKER =
new IOException("HTTP/2 connection idle timed out - no error");
static {
// remove the stacktrace from the marker exception instances
NO_ERROR_MARKER.setStackTrace(new StackTraceElement[0]);
NO_ERROR_IDLE_TIMED_OUT_MARKER.setStackTrace(new StackTraceElement[0]);
}
private static final NoError INSTANCE = new NoError(false);
private static final NoError IDLE_TIMED_OUT = new NoError(true);
private final boolean idleTimedOut;
private NoError(final boolean idleTimedOut) {
super(ErrorFrame.NO_ERROR,
idleTimedOut ? NO_ERROR_IDLE_TIMED_OUT_MARKER : NO_ERROR_MARKER);
this.idleTimedOut = idleTimedOut;
setPeerVisibleReason(idleTimedOut ? "idle timed out" : "no error");
}
@Override
public boolean isAbnormalClose() {
return false;
}
@Override
public String toString() {
return this.idleTimedOut
? "No error - idle timed out"
: "No error - normal termination";
}
}
private static sealed class H2StandardError extends Http2TerminationCause {
private H2StandardError(final int errCode, final String msg) {
super(errCode, msg);
setPeerVisibleReason(ErrorFrame.stringForCode(errCode));
}
private H2StandardError(final int errCode, final Throwable cause) {
super(errCode, cause);
setPeerVisibleReason(ErrorFrame.stringForCode(errCode));
}
@Override
public boolean isAbnormalClose() {
return getCloseCode() != ErrorFrame.NO_ERROR;
}
@Override
public String toString() {
return ErrorFrame.stringForCode(this.getCloseCode());
}
}
private static final class ProtocolError extends H2StandardError {
private ProtocolError(final String msg) {
this(msg, false);
}
private ProtocolError(final String msg, final boolean flowControlError) {
super(flowControlError
? ErrorFrame.FLOW_CONTROL_ERROR
: ErrorFrame.PROTOCOL_ERROR,
new ProtocolException(msg));
}
private ProtocolError(final ProtocolException pe) {
super(ErrorFrame.PROTOCOL_ERROR, pe);
}
@Override
public boolean isAbnormalClose() {
return true;
}
@Override
public String toString() {
return "Protocol error - " + this.getLogMsg();
}
}
private static final class InternalError extends Http2TerminationCause {
private InternalError(final Throwable cause) {
super(ErrorFrame.INTERNAL_ERROR, cause);
}
@Override
public boolean isAbnormalClose() {
return true;
}
@Override
public String toString() {
return "Internal error - " + this.getLogMsg();
}
}
}