diff options
Diffstat (limited to 'vendor/google.golang.org/grpc/transport/http_util.go')
-rw-r--r-- | vendor/google.golang.org/grpc/transport/http_util.go | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/vendor/google.golang.org/grpc/transport/http_util.go b/vendor/google.golang.org/grpc/transport/http_util.go new file mode 100644 index 0000000..685c6fb --- /dev/null +++ b/vendor/google.golang.org/grpc/transport/http_util.go | |||
@@ -0,0 +1,597 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Copyright 2014 gRPC authors. | ||
4 | * | ||
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | * you may not use this file except in compliance with the License. | ||
7 | * You may obtain a copy of the License at | ||
8 | * | ||
9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | * | ||
11 | * Unless required by applicable law or agreed to in writing, software | ||
12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | * See the License for the specific language governing permissions and | ||
15 | * limitations under the License. | ||
16 | * | ||
17 | */ | ||
18 | |||
19 | package transport | ||
20 | |||
21 | import ( | ||
22 | "bufio" | ||
23 | "bytes" | ||
24 | "encoding/base64" | ||
25 | "fmt" | ||
26 | "io" | ||
27 | "net" | ||
28 | "net/http" | ||
29 | "strconv" | ||
30 | "strings" | ||
31 | "sync/atomic" | ||
32 | "time" | ||
33 | |||
34 | "github.com/golang/protobuf/proto" | ||
35 | "golang.org/x/net/http2" | ||
36 | "golang.org/x/net/http2/hpack" | ||
37 | spb "google.golang.org/genproto/googleapis/rpc/status" | ||
38 | "google.golang.org/grpc/codes" | ||
39 | "google.golang.org/grpc/status" | ||
40 | ) | ||
41 | |||
42 | const ( | ||
43 | // http2MaxFrameLen specifies the max length of a HTTP2 frame. | ||
44 | http2MaxFrameLen = 16384 // 16KB frame | ||
45 | // http://http2.github.io/http2-spec/#SettingValues | ||
46 | http2InitHeaderTableSize = 4096 | ||
47 | // http2IOBufSize specifies the buffer size for sending frames. | ||
48 | http2IOBufSize = 32 * 1024 | ||
49 | ) | ||
50 | |||
51 | var ( | ||
52 | clientPreface = []byte(http2.ClientPreface) | ||
53 | http2ErrConvTab = map[http2.ErrCode]codes.Code{ | ||
54 | http2.ErrCodeNo: codes.Internal, | ||
55 | http2.ErrCodeProtocol: codes.Internal, | ||
56 | http2.ErrCodeInternal: codes.Internal, | ||
57 | http2.ErrCodeFlowControl: codes.ResourceExhausted, | ||
58 | http2.ErrCodeSettingsTimeout: codes.Internal, | ||
59 | http2.ErrCodeStreamClosed: codes.Internal, | ||
60 | http2.ErrCodeFrameSize: codes.Internal, | ||
61 | http2.ErrCodeRefusedStream: codes.Unavailable, | ||
62 | http2.ErrCodeCancel: codes.Canceled, | ||
63 | http2.ErrCodeCompression: codes.Internal, | ||
64 | http2.ErrCodeConnect: codes.Internal, | ||
65 | http2.ErrCodeEnhanceYourCalm: codes.ResourceExhausted, | ||
66 | http2.ErrCodeInadequateSecurity: codes.PermissionDenied, | ||
67 | http2.ErrCodeHTTP11Required: codes.FailedPrecondition, | ||
68 | } | ||
69 | statusCodeConvTab = map[codes.Code]http2.ErrCode{ | ||
70 | codes.Internal: http2.ErrCodeInternal, | ||
71 | codes.Canceled: http2.ErrCodeCancel, | ||
72 | codes.Unavailable: http2.ErrCodeRefusedStream, | ||
73 | codes.ResourceExhausted: http2.ErrCodeEnhanceYourCalm, | ||
74 | codes.PermissionDenied: http2.ErrCodeInadequateSecurity, | ||
75 | } | ||
76 | httpStatusConvTab = map[int]codes.Code{ | ||
77 | // 400 Bad Request - INTERNAL. | ||
78 | http.StatusBadRequest: codes.Internal, | ||
79 | // 401 Unauthorized - UNAUTHENTICATED. | ||
80 | http.StatusUnauthorized: codes.Unauthenticated, | ||
81 | // 403 Forbidden - PERMISSION_DENIED. | ||
82 | http.StatusForbidden: codes.PermissionDenied, | ||
83 | // 404 Not Found - UNIMPLEMENTED. | ||
84 | http.StatusNotFound: codes.Unimplemented, | ||
85 | // 429 Too Many Requests - UNAVAILABLE. | ||
86 | http.StatusTooManyRequests: codes.Unavailable, | ||
87 | // 502 Bad Gateway - UNAVAILABLE. | ||
88 | http.StatusBadGateway: codes.Unavailable, | ||
89 | // 503 Service Unavailable - UNAVAILABLE. | ||
90 | http.StatusServiceUnavailable: codes.Unavailable, | ||
91 | // 504 Gateway timeout - UNAVAILABLE. | ||
92 | http.StatusGatewayTimeout: codes.Unavailable, | ||
93 | } | ||
94 | ) | ||
95 | |||
96 | // Records the states during HPACK decoding. Must be reset once the | ||
97 | // decoding of the entire headers are finished. | ||
98 | type decodeState struct { | ||
99 | encoding string | ||
100 | // statusGen caches the stream status received from the trailer the server | ||
101 | // sent. Client side only. Do not access directly. After all trailers are | ||
102 | // parsed, use the status method to retrieve the status. | ||
103 | statusGen *status.Status | ||
104 | // rawStatusCode and rawStatusMsg are set from the raw trailer fields and are not | ||
105 | // intended for direct access outside of parsing. | ||
106 | rawStatusCode *int | ||
107 | rawStatusMsg string | ||
108 | httpStatus *int | ||
109 | // Server side only fields. | ||
110 | timeoutSet bool | ||
111 | timeout time.Duration | ||
112 | method string | ||
113 | // key-value metadata map from the peer. | ||
114 | mdata map[string][]string | ||
115 | } | ||
116 | |||
117 | // isReservedHeader checks whether hdr belongs to HTTP2 headers | ||
118 | // reserved by gRPC protocol. Any other headers are classified as the | ||
119 | // user-specified metadata. | ||
120 | func isReservedHeader(hdr string) bool { | ||
121 | if hdr != "" && hdr[0] == ':' { | ||
122 | return true | ||
123 | } | ||
124 | switch hdr { | ||
125 | case "content-type", | ||
126 | "grpc-message-type", | ||
127 | "grpc-encoding", | ||
128 | "grpc-message", | ||
129 | "grpc-status", | ||
130 | "grpc-timeout", | ||
131 | "grpc-status-details-bin", | ||
132 | "te": | ||
133 | return true | ||
134 | default: | ||
135 | return false | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // isWhitelistedPseudoHeader checks whether hdr belongs to HTTP2 pseudoheaders | ||
140 | // that should be propagated into metadata visible to users. | ||
141 | func isWhitelistedPseudoHeader(hdr string) bool { | ||
142 | switch hdr { | ||
143 | case ":authority": | ||
144 | return true | ||
145 | default: | ||
146 | return false | ||
147 | } | ||
148 | } | ||
149 | |||
150 | func validContentType(t string) bool { | ||
151 | e := "application/grpc" | ||
152 | if !strings.HasPrefix(t, e) { | ||
153 | return false | ||
154 | } | ||
155 | // Support variations on the content-type | ||
156 | // (e.g. "application/grpc+blah", "application/grpc;blah"). | ||
157 | if len(t) > len(e) && t[len(e)] != '+' && t[len(e)] != ';' { | ||
158 | return false | ||
159 | } | ||
160 | return true | ||
161 | } | ||
162 | |||
163 | func (d *decodeState) status() *status.Status { | ||
164 | if d.statusGen == nil { | ||
165 | // No status-details were provided; generate status using code/msg. | ||
166 | d.statusGen = status.New(codes.Code(int32(*(d.rawStatusCode))), d.rawStatusMsg) | ||
167 | } | ||
168 | return d.statusGen | ||
169 | } | ||
170 | |||
171 | const binHdrSuffix = "-bin" | ||
172 | |||
173 | func encodeBinHeader(v []byte) string { | ||
174 | return base64.RawStdEncoding.EncodeToString(v) | ||
175 | } | ||
176 | |||
177 | func decodeBinHeader(v string) ([]byte, error) { | ||
178 | if len(v)%4 == 0 { | ||
179 | // Input was padded, or padding was not necessary. | ||
180 | return base64.StdEncoding.DecodeString(v) | ||
181 | } | ||
182 | return base64.RawStdEncoding.DecodeString(v) | ||
183 | } | ||
184 | |||
185 | func encodeMetadataHeader(k, v string) string { | ||
186 | if strings.HasSuffix(k, binHdrSuffix) { | ||
187 | return encodeBinHeader(([]byte)(v)) | ||
188 | } | ||
189 | return v | ||
190 | } | ||
191 | |||
192 | func decodeMetadataHeader(k, v string) (string, error) { | ||
193 | if strings.HasSuffix(k, binHdrSuffix) { | ||
194 | b, err := decodeBinHeader(v) | ||
195 | return string(b), err | ||
196 | } | ||
197 | return v, nil | ||
198 | } | ||
199 | |||
200 | func (d *decodeState) decodeResponseHeader(frame *http2.MetaHeadersFrame) error { | ||
201 | for _, hf := range frame.Fields { | ||
202 | if err := d.processHeaderField(hf); err != nil { | ||
203 | return err | ||
204 | } | ||
205 | } | ||
206 | |||
207 | // If grpc status exists, no need to check further. | ||
208 | if d.rawStatusCode != nil || d.statusGen != nil { | ||
209 | return nil | ||
210 | } | ||
211 | |||
212 | // If grpc status doesn't exist and http status doesn't exist, | ||
213 | // then it's a malformed header. | ||
214 | if d.httpStatus == nil { | ||
215 | return streamErrorf(codes.Internal, "malformed header: doesn't contain status(gRPC or HTTP)") | ||
216 | } | ||
217 | |||
218 | if *(d.httpStatus) != http.StatusOK { | ||
219 | code, ok := httpStatusConvTab[*(d.httpStatus)] | ||
220 | if !ok { | ||
221 | code = codes.Unknown | ||
222 | } | ||
223 | return streamErrorf(code, http.StatusText(*(d.httpStatus))) | ||
224 | } | ||
225 | |||
226 | // gRPC status doesn't exist and http status is OK. | ||
227 | // Set rawStatusCode to be unknown and return nil error. | ||
228 | // So that, if the stream has ended this Unknown status | ||
229 | // will be propogated to the user. | ||
230 | // Otherwise, it will be ignored. In which case, status from | ||
231 | // a later trailer, that has StreamEnded flag set, is propogated. | ||
232 | code := int(codes.Unknown) | ||
233 | d.rawStatusCode = &code | ||
234 | return nil | ||
235 | |||
236 | } | ||
237 | |||
238 | func (d *decodeState) processHeaderField(f hpack.HeaderField) error { | ||
239 | switch f.Name { | ||
240 | case "content-type": | ||
241 | if !validContentType(f.Value) { | ||
242 | return streamErrorf(codes.FailedPrecondition, "transport: received the unexpected content-type %q", f.Value) | ||
243 | } | ||
244 | case "grpc-encoding": | ||
245 | d.encoding = f.Value | ||
246 | case "grpc-status": | ||
247 | code, err := strconv.Atoi(f.Value) | ||
248 | if err != nil { | ||
249 | return streamErrorf(codes.Internal, "transport: malformed grpc-status: %v", err) | ||
250 | } | ||
251 | d.rawStatusCode = &code | ||
252 | case "grpc-message": | ||
253 | d.rawStatusMsg = decodeGrpcMessage(f.Value) | ||
254 | case "grpc-status-details-bin": | ||
255 | v, err := decodeBinHeader(f.Value) | ||
256 | if err != nil { | ||
257 | return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err) | ||
258 | } | ||
259 | s := &spb.Status{} | ||
260 | if err := proto.Unmarshal(v, s); err != nil { | ||
261 | return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err) | ||
262 | } | ||
263 | d.statusGen = status.FromProto(s) | ||
264 | case "grpc-timeout": | ||
265 | d.timeoutSet = true | ||
266 | var err error | ||
267 | if d.timeout, err = decodeTimeout(f.Value); err != nil { | ||
268 | return streamErrorf(codes.Internal, "transport: malformed time-out: %v", err) | ||
269 | } | ||
270 | case ":path": | ||
271 | d.method = f.Value | ||
272 | case ":status": | ||
273 | code, err := strconv.Atoi(f.Value) | ||
274 | if err != nil { | ||
275 | return streamErrorf(codes.Internal, "transport: malformed http-status: %v", err) | ||
276 | } | ||
277 | d.httpStatus = &code | ||
278 | default: | ||
279 | if !isReservedHeader(f.Name) || isWhitelistedPseudoHeader(f.Name) { | ||
280 | if d.mdata == nil { | ||
281 | d.mdata = make(map[string][]string) | ||
282 | } | ||
283 | v, err := decodeMetadataHeader(f.Name, f.Value) | ||
284 | if err != nil { | ||
285 | errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err) | ||
286 | return nil | ||
287 | } | ||
288 | d.mdata[f.Name] = append(d.mdata[f.Name], v) | ||
289 | } | ||
290 | } | ||
291 | return nil | ||
292 | } | ||
293 | |||
294 | type timeoutUnit uint8 | ||
295 | |||
296 | const ( | ||
297 | hour timeoutUnit = 'H' | ||
298 | minute timeoutUnit = 'M' | ||
299 | second timeoutUnit = 'S' | ||
300 | millisecond timeoutUnit = 'm' | ||
301 | microsecond timeoutUnit = 'u' | ||
302 | nanosecond timeoutUnit = 'n' | ||
303 | ) | ||
304 | |||
305 | func timeoutUnitToDuration(u timeoutUnit) (d time.Duration, ok bool) { | ||
306 | switch u { | ||
307 | case hour: | ||
308 | return time.Hour, true | ||
309 | case minute: | ||
310 | return time.Minute, true | ||
311 | case second: | ||
312 | return time.Second, true | ||
313 | case millisecond: | ||
314 | return time.Millisecond, true | ||
315 | case microsecond: | ||
316 | return time.Microsecond, true | ||
317 | case nanosecond: | ||
318 | return time.Nanosecond, true | ||
319 | default: | ||
320 | } | ||
321 | return | ||
322 | } | ||
323 | |||
324 | const maxTimeoutValue int64 = 100000000 - 1 | ||
325 | |||
326 | // div does integer division and round-up the result. Note that this is | ||
327 | // equivalent to (d+r-1)/r but has less chance to overflow. | ||
328 | func div(d, r time.Duration) int64 { | ||
329 | if m := d % r; m > 0 { | ||
330 | return int64(d/r + 1) | ||
331 | } | ||
332 | return int64(d / r) | ||
333 | } | ||
334 | |||
335 | // TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it. | ||
336 | func encodeTimeout(t time.Duration) string { | ||
337 | if t <= 0 { | ||
338 | return "0n" | ||
339 | } | ||
340 | if d := div(t, time.Nanosecond); d <= maxTimeoutValue { | ||
341 | return strconv.FormatInt(d, 10) + "n" | ||
342 | } | ||
343 | if d := div(t, time.Microsecond); d <= maxTimeoutValue { | ||
344 | return strconv.FormatInt(d, 10) + "u" | ||
345 | } | ||
346 | if d := div(t, time.Millisecond); d <= maxTimeoutValue { | ||
347 | return strconv.FormatInt(d, 10) + "m" | ||
348 | } | ||
349 | if d := div(t, time.Second); d <= maxTimeoutValue { | ||
350 | return strconv.FormatInt(d, 10) + "S" | ||
351 | } | ||
352 | if d := div(t, time.Minute); d <= maxTimeoutValue { | ||
353 | return strconv.FormatInt(d, 10) + "M" | ||
354 | } | ||
355 | // Note that maxTimeoutValue * time.Hour > MaxInt64. | ||
356 | return strconv.FormatInt(div(t, time.Hour), 10) + "H" | ||
357 | } | ||
358 | |||
359 | func decodeTimeout(s string) (time.Duration, error) { | ||
360 | size := len(s) | ||
361 | if size < 2 { | ||
362 | return 0, fmt.Errorf("transport: timeout string is too short: %q", s) | ||
363 | } | ||
364 | unit := timeoutUnit(s[size-1]) | ||
365 | d, ok := timeoutUnitToDuration(unit) | ||
366 | if !ok { | ||
367 | return 0, fmt.Errorf("transport: timeout unit is not recognized: %q", s) | ||
368 | } | ||
369 | t, err := strconv.ParseInt(s[:size-1], 10, 64) | ||
370 | if err != nil { | ||
371 | return 0, err | ||
372 | } | ||
373 | return d * time.Duration(t), nil | ||
374 | } | ||
375 | |||
376 | const ( | ||
377 | spaceByte = ' ' | ||
378 | tildaByte = '~' | ||
379 | percentByte = '%' | ||
380 | ) | ||
381 | |||
382 | // encodeGrpcMessage is used to encode status code in header field | ||
383 | // "grpc-message". | ||
384 | // It checks to see if each individual byte in msg is an | ||
385 | // allowable byte, and then either percent encoding or passing it through. | ||
386 | // When percent encoding, the byte is converted into hexadecimal notation | ||
387 | // with a '%' prepended. | ||
388 | func encodeGrpcMessage(msg string) string { | ||
389 | if msg == "" { | ||
390 | return "" | ||
391 | } | ||
392 | lenMsg := len(msg) | ||
393 | for i := 0; i < lenMsg; i++ { | ||
394 | c := msg[i] | ||
395 | if !(c >= spaceByte && c < tildaByte && c != percentByte) { | ||
396 | return encodeGrpcMessageUnchecked(msg) | ||
397 | } | ||
398 | } | ||
399 | return msg | ||
400 | } | ||
401 | |||
402 | func encodeGrpcMessageUnchecked(msg string) string { | ||
403 | var buf bytes.Buffer | ||
404 | lenMsg := len(msg) | ||
405 | for i := 0; i < lenMsg; i++ { | ||
406 | c := msg[i] | ||
407 | if c >= spaceByte && c < tildaByte && c != percentByte { | ||
408 | buf.WriteByte(c) | ||
409 | } else { | ||
410 | buf.WriteString(fmt.Sprintf("%%%02X", c)) | ||
411 | } | ||
412 | } | ||
413 | return buf.String() | ||
414 | } | ||
415 | |||
416 | // decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage. | ||
417 | func decodeGrpcMessage(msg string) string { | ||
418 | if msg == "" { | ||
419 | return "" | ||
420 | } | ||
421 | lenMsg := len(msg) | ||
422 | for i := 0; i < lenMsg; i++ { | ||
423 | if msg[i] == percentByte && i+2 < lenMsg { | ||
424 | return decodeGrpcMessageUnchecked(msg) | ||
425 | } | ||
426 | } | ||
427 | return msg | ||
428 | } | ||
429 | |||
430 | func decodeGrpcMessageUnchecked(msg string) string { | ||
431 | var buf bytes.Buffer | ||
432 | lenMsg := len(msg) | ||
433 | for i := 0; i < lenMsg; i++ { | ||
434 | c := msg[i] | ||
435 | if c == percentByte && i+2 < lenMsg { | ||
436 | parsed, err := strconv.ParseUint(msg[i+1:i+3], 16, 8) | ||
437 | if err != nil { | ||
438 | buf.WriteByte(c) | ||
439 | } else { | ||
440 | buf.WriteByte(byte(parsed)) | ||
441 | i += 2 | ||
442 | } | ||
443 | } else { | ||
444 | buf.WriteByte(c) | ||
445 | } | ||
446 | } | ||
447 | return buf.String() | ||
448 | } | ||
449 | |||
450 | type framer struct { | ||
451 | numWriters int32 | ||
452 | reader io.Reader | ||
453 | writer *bufio.Writer | ||
454 | fr *http2.Framer | ||
455 | } | ||
456 | |||
457 | func newFramer(conn net.Conn) *framer { | ||
458 | f := &framer{ | ||
459 | reader: bufio.NewReaderSize(conn, http2IOBufSize), | ||
460 | writer: bufio.NewWriterSize(conn, http2IOBufSize), | ||
461 | } | ||
462 | f.fr = http2.NewFramer(f.writer, f.reader) | ||
463 | // Opt-in to Frame reuse API on framer to reduce garbage. | ||
464 | // Frames aren't safe to read from after a subsequent call to ReadFrame. | ||
465 | f.fr.SetReuseFrames() | ||
466 | f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil) | ||
467 | return f | ||
468 | } | ||
469 | |||
470 | func (f *framer) adjustNumWriters(i int32) int32 { | ||
471 | return atomic.AddInt32(&f.numWriters, i) | ||
472 | } | ||
473 | |||
474 | // The following writeXXX functions can only be called when the caller gets | ||
475 | // unblocked from writableChan channel (i.e., owns the privilege to write). | ||
476 | |||
477 | func (f *framer) writeContinuation(forceFlush bool, streamID uint32, endHeaders bool, headerBlockFragment []byte) error { | ||
478 | if err := f.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil { | ||
479 | return err | ||
480 | } | ||
481 | if forceFlush { | ||
482 | return f.writer.Flush() | ||
483 | } | ||
484 | return nil | ||
485 | } | ||
486 | |||
487 | func (f *framer) writeData(forceFlush bool, streamID uint32, endStream bool, data []byte) error { | ||
488 | if err := f.fr.WriteData(streamID, endStream, data); err != nil { | ||
489 | return err | ||
490 | } | ||
491 | if forceFlush { | ||
492 | return f.writer.Flush() | ||
493 | } | ||
494 | return nil | ||
495 | } | ||
496 | |||
497 | func (f *framer) writeGoAway(forceFlush bool, maxStreamID uint32, code http2.ErrCode, debugData []byte) error { | ||
498 | if err := f.fr.WriteGoAway(maxStreamID, code, debugData); err != nil { | ||
499 | return err | ||
500 | } | ||
501 | if forceFlush { | ||
502 | return f.writer.Flush() | ||
503 | } | ||
504 | return nil | ||
505 | } | ||
506 | |||
507 | func (f *framer) writeHeaders(forceFlush bool, p http2.HeadersFrameParam) error { | ||
508 | if err := f.fr.WriteHeaders(p); err != nil { | ||
509 | return err | ||
510 | } | ||
511 | if forceFlush { | ||
512 | return f.writer.Flush() | ||
513 | } | ||
514 | return nil | ||
515 | } | ||
516 | |||
517 | func (f *framer) writePing(forceFlush, ack bool, data [8]byte) error { | ||
518 | if err := f.fr.WritePing(ack, data); err != nil { | ||
519 | return err | ||
520 | } | ||
521 | if forceFlush { | ||
522 | return f.writer.Flush() | ||
523 | } | ||
524 | return nil | ||
525 | } | ||
526 | |||
527 | func (f *framer) writePriority(forceFlush bool, streamID uint32, p http2.PriorityParam) error { | ||
528 | if err := f.fr.WritePriority(streamID, p); err != nil { | ||
529 | return err | ||
530 | } | ||
531 | if forceFlush { | ||
532 | return f.writer.Flush() | ||
533 | } | ||
534 | return nil | ||
535 | } | ||
536 | |||
537 | func (f *framer) writePushPromise(forceFlush bool, p http2.PushPromiseParam) error { | ||
538 | if err := f.fr.WritePushPromise(p); err != nil { | ||
539 | return err | ||
540 | } | ||
541 | if forceFlush { | ||
542 | return f.writer.Flush() | ||
543 | } | ||
544 | return nil | ||
545 | } | ||
546 | |||
547 | func (f *framer) writeRSTStream(forceFlush bool, streamID uint32, code http2.ErrCode) error { | ||
548 | if err := f.fr.WriteRSTStream(streamID, code); err != nil { | ||
549 | return err | ||
550 | } | ||
551 | if forceFlush { | ||
552 | return f.writer.Flush() | ||
553 | } | ||
554 | return nil | ||
555 | } | ||
556 | |||
557 | func (f *framer) writeSettings(forceFlush bool, settings ...http2.Setting) error { | ||
558 | if err := f.fr.WriteSettings(settings...); err != nil { | ||
559 | return err | ||
560 | } | ||
561 | if forceFlush { | ||
562 | return f.writer.Flush() | ||
563 | } | ||
564 | return nil | ||
565 | } | ||
566 | |||
567 | func (f *framer) writeSettingsAck(forceFlush bool) error { | ||
568 | if err := f.fr.WriteSettingsAck(); err != nil { | ||
569 | return err | ||
570 | } | ||
571 | if forceFlush { | ||
572 | return f.writer.Flush() | ||
573 | } | ||
574 | return nil | ||
575 | } | ||
576 | |||
577 | func (f *framer) writeWindowUpdate(forceFlush bool, streamID, incr uint32) error { | ||
578 | if err := f.fr.WriteWindowUpdate(streamID, incr); err != nil { | ||
579 | return err | ||
580 | } | ||
581 | if forceFlush { | ||
582 | return f.writer.Flush() | ||
583 | } | ||
584 | return nil | ||
585 | } | ||
586 | |||
587 | func (f *framer) flushWrite() error { | ||
588 | return f.writer.Flush() | ||
589 | } | ||
590 | |||
591 | func (f *framer) readFrame() (http2.Frame, error) { | ||
592 | return f.fr.ReadFrame() | ||
593 | } | ||
594 | |||
595 | func (f *framer) errorDetail() error { | ||
596 | return f.fr.ErrorDetail() | ||
597 | } | ||