• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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