001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client; 018 019import java.io.*; 020import java.net.*; 021import java.nio.channels.*; 022import java.util.*; 023 024/** 025 * Thrown by {@link HttpTransport#execute(TransportRequest)} when a network-level error occurs (e.g. connection 026 * refused, timeout, SSL handshake failure). 027 * 028 * <p> 029 * This exception wraps the underlying transport library's exception so that callers do not need to handle 030 * Apache HttpClient, OkHttp, or JDK {@code HttpClient} exceptions directly. 031 * 032 * @since 9.2.1 033 */ 034public class TransportException extends IOException { 035 036 private static final long serialVersionUID = 1L; 037 038 /** 039 * Constructor. 040 * 041 * @param message The error message. 042 */ 043 public TransportException(String message) { 044 super(message); 045 } 046 047 /** 048 * Constructor. 049 * 050 * @param message The error message. 051 * @param cause The underlying cause. Can be <jk>null</jk> (no cause is chained). 052 */ 053 public TransportException(String message, Throwable cause) { 054 super(message, cause); 055 } 056 057 /** 058 * Constructor. 059 * 060 * @param cause The underlying cause. Can be <jk>null</jk> (no cause is chained). 061 */ 062 public TransportException(Throwable cause) { 063 super(cause); 064 } 065 066 /** 067 * Returns <jk>true</jk> if the given throwable (or any cause in its chain) is the signature of a pre-response 068 * stale pooled-connection failure — the server tore down a kept-alive connection before sending any response 069 * bytes. 070 * 071 * <p> 072 * This is recognized across transports as an {@link EOFException} or {@link ClosedChannelException}, a 073 * connection-reset {@link SocketException}, or the JDK {@link java.net.http.HttpClient}'s 074 * {@code "HTTP/1.1 header parser received no bytes"} message. Such a failure is safe to replay exactly once on 075 * a fresh connection provided the request cannot cause a duplicate side effect (an idempotent HTTP method, or — 076 * at a higher protocol layer — a side-effect-free operation). 077 * 078 * @param t The throwable to inspect. Can be <jk>null</jk> (returns <jk>false</jk>). 079 * @return <jk>true</jk> if the throwable indicates a pre-response stale-connection failure. 080 */ 081 public static boolean isStaleConnectionFailure(Throwable t) { 082 for (var t2 = t; t2 != null; t2 = t2.getCause()) { 083 if (t2 instanceof EOFException || t2 instanceof ClosedChannelException) 084 return true; 085 var msg = t2.getMessage(); 086 if (msg != null) { 087 var m = msg.toLowerCase(Locale.ROOT); 088 if (m.contains("received no bytes") || (t2 instanceof SocketException && m.contains("reset"))) 089 return true; 090 } 091 } 092 return false; 093 } 094}