1 /* 2 * Copyright 2018 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.okhttp; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.base.Preconditions; 23 import io.grpc.okhttp.internal.framed.ErrorCode; 24 import io.grpc.okhttp.internal.framed.FrameWriter; 25 import io.grpc.okhttp.internal.framed.Header; 26 import io.grpc.okhttp.internal.framed.Settings; 27 import java.io.IOException; 28 import java.util.List; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 import okio.Buffer; 32 import okio.ByteString; 33 34 /** 35 * FrameWriter that propagates IOExceptions via callback instead of throwing. This allows 36 * centralized handling of errors. Exceptions only impact the single call that throws them; callers 37 * should be sure to kill the connection after an exception (potentially after sending a GOAWAY) as 38 * otherwise additional frames after the failed/omitted one could cause HTTP/2 confusion. 39 */ 40 final class ExceptionHandlingFrameWriter implements FrameWriter { 41 42 private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); 43 44 private final TransportExceptionHandler transportExceptionHandler; 45 46 private final FrameWriter frameWriter; 47 48 private final OkHttpFrameLogger frameLogger = 49 new OkHttpFrameLogger(Level.FINE, OkHttpClientTransport.class); 50 ExceptionHandlingFrameWriter( TransportExceptionHandler transportExceptionHandler, FrameWriter frameWriter)51 ExceptionHandlingFrameWriter( 52 TransportExceptionHandler transportExceptionHandler, FrameWriter frameWriter) { 53 this.transportExceptionHandler = 54 checkNotNull(transportExceptionHandler, "transportExceptionHandler"); 55 this.frameWriter = Preconditions.checkNotNull(frameWriter, "frameWriter"); 56 } 57 58 @Override connectionPreface()59 public void connectionPreface() { 60 try { 61 frameWriter.connectionPreface(); 62 } catch (IOException e) { 63 transportExceptionHandler.onException(e); 64 } 65 } 66 67 @Override ackSettings(Settings peerSettings)68 public void ackSettings(Settings peerSettings) { 69 frameLogger.logSettingsAck(OkHttpFrameLogger.Direction.OUTBOUND); 70 try { 71 frameWriter.ackSettings(peerSettings); 72 } catch (IOException e) { 73 transportExceptionHandler.onException(e); 74 } 75 } 76 77 @Override pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)78 public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) { 79 frameLogger.logPushPromise(OkHttpFrameLogger.Direction.OUTBOUND, 80 streamId, promisedStreamId, requestHeaders); 81 try { 82 frameWriter.pushPromise(streamId, promisedStreamId, requestHeaders); 83 } catch (IOException e) { 84 transportExceptionHandler.onException(e); 85 } 86 } 87 88 @Override flush()89 public void flush() { 90 try { 91 frameWriter.flush(); 92 } catch (IOException e) { 93 transportExceptionHandler.onException(e); 94 } 95 } 96 97 @Override synStream( boolean outFinished, boolean inFinished, int streamId, int associatedStreamId, List<Header> headerBlock)98 public void synStream( 99 boolean outFinished, 100 boolean inFinished, 101 int streamId, 102 int associatedStreamId, 103 List<Header> headerBlock) { 104 try { 105 frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, headerBlock); 106 } catch (IOException e) { 107 transportExceptionHandler.onException(e); 108 } 109 } 110 111 @Override synReply(boolean outFinished, int streamId, List<Header> headerBlock)112 public void synReply(boolean outFinished, int streamId, 113 List<Header> headerBlock) { 114 try { 115 frameWriter.synReply(outFinished, streamId, headerBlock); 116 } catch (IOException e) { 117 transportExceptionHandler.onException(e); 118 } 119 } 120 121 @Override headers(int streamId, List<Header> headerBlock)122 public void headers(int streamId, List<Header> headerBlock) { 123 frameLogger.logHeaders(OkHttpFrameLogger.Direction.OUTBOUND, streamId, headerBlock, false); 124 try { 125 frameWriter.headers(streamId, headerBlock); 126 } catch (IOException e) { 127 transportExceptionHandler.onException(e); 128 } 129 } 130 131 @Override rstStream(int streamId, ErrorCode errorCode)132 public void rstStream(int streamId, ErrorCode errorCode) { 133 frameLogger.logRstStream(OkHttpFrameLogger.Direction.OUTBOUND, streamId, errorCode); 134 try { 135 frameWriter.rstStream(streamId, errorCode); 136 } catch (IOException e) { 137 transportExceptionHandler.onException(e); 138 } 139 } 140 141 @Override maxDataLength()142 public int maxDataLength() { 143 return frameWriter.maxDataLength(); 144 } 145 146 @Override data(boolean outFinished, int streamId, Buffer source, int byteCount)147 public void data(boolean outFinished, int streamId, Buffer source, int byteCount) { 148 frameLogger.logData(OkHttpFrameLogger.Direction.OUTBOUND, 149 streamId, source.buffer(), byteCount, outFinished); 150 try { 151 frameWriter.data(outFinished, streamId, source, byteCount); 152 } catch (IOException e) { 153 transportExceptionHandler.onException(e); 154 } 155 } 156 157 @Override settings(Settings okHttpSettings)158 public void settings(Settings okHttpSettings) { 159 frameLogger.logSettings(OkHttpFrameLogger.Direction.OUTBOUND, okHttpSettings); 160 try { 161 frameWriter.settings(okHttpSettings); 162 } catch (IOException e) { 163 transportExceptionHandler.onException(e); 164 } 165 } 166 167 @Override ping(boolean ack, int payload1, int payload2)168 public void ping(boolean ack, int payload1, int payload2) { 169 if (ack) { 170 frameLogger.logPingAck(OkHttpFrameLogger.Direction.OUTBOUND, 171 ((long) payload1 << 32) | (payload2 & 0xFFFFFFFFL)); 172 } else { 173 frameLogger.logPing(OkHttpFrameLogger.Direction.OUTBOUND, 174 ((long) payload1 << 32) | (payload2 & 0xFFFFFFFFL)); 175 } 176 try { 177 frameWriter.ping(ack, payload1, payload2); 178 } catch (IOException e) { 179 transportExceptionHandler.onException(e); 180 } 181 } 182 183 @Override goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)184 public void goAway(int lastGoodStreamId, ErrorCode errorCode, 185 byte[] debugData) { 186 frameLogger.logGoAway(OkHttpFrameLogger.Direction.OUTBOUND, 187 lastGoodStreamId, errorCode, ByteString.of(debugData)); 188 try { 189 frameWriter.goAway(lastGoodStreamId, errorCode, debugData); 190 // Flush it since after goAway, we are likely to close this writer. 191 frameWriter.flush(); 192 } catch (IOException e) { 193 transportExceptionHandler.onException(e); 194 } 195 } 196 197 @Override windowUpdate(int streamId, long windowSizeIncrement)198 public void windowUpdate(int streamId, long windowSizeIncrement) { 199 frameLogger.logWindowsUpdate(OkHttpFrameLogger.Direction.OUTBOUND, 200 streamId, windowSizeIncrement); 201 try { 202 frameWriter.windowUpdate(streamId, windowSizeIncrement); 203 } catch (IOException e) { 204 transportExceptionHandler.onException(e); 205 } 206 } 207 208 @Override close()209 public void close() { 210 try { 211 frameWriter.close(); 212 } catch (IOException e) { 213 log.log(getLogLevel(e), "Failed closing connection", e); 214 } 215 } 216 217 /** 218 * Accepts a throwable and returns the appropriate logging level. Uninteresting exceptions 219 * should not clutter the log. 220 */ 221 @VisibleForTesting getLogLevel(Throwable t)222 static Level getLogLevel(Throwable t) { 223 if (t.getClass().equals(IOException.class)) { 224 return Level.FINE; 225 } 226 return Level.INFO; 227 } 228 229 /** A class that handles transport exception. */ 230 interface TransportExceptionHandler { 231 /** Handles exception. */ onException(Throwable throwable)232 void onException(Throwable throwable); 233 } 234 } 235