OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / net / http2 / h2i / h2i.go
1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // +build !plan9,!solaris
6
7 /*
8 The h2i command is an interactive HTTP/2 console.
9
10 Usage:
11   $ h2i [flags] <hostname>
12
13 Interactive commands in the console: (all parts case-insensitive)
14
15   ping [data]
16   settings ack
17   settings FOO=n BAR=z
18   headers      (open a new stream by typing HTTP/1.1)
19 */
20 package main
21
22 import (
23         "bufio"
24         "bytes"
25         "crypto/tls"
26         "errors"
27         "flag"
28         "fmt"
29         "io"
30         "log"
31         "net"
32         "net/http"
33         "os"
34         "regexp"
35         "strconv"
36         "strings"
37
38         "golang.org/x/crypto/ssh/terminal"
39         "golang.org/x/net/http2"
40         "golang.org/x/net/http2/hpack"
41 )
42
43 // Flags
44 var (
45         flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
46         flagInsecure  = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
47         flagSettings  = flag.String("settings", "empty", "comma-separated list of KEY=value settings for the initial SETTINGS frame. The magic value 'empty' sends an empty initial settings frame, and the magic value 'omit' causes no initial settings frame to be sent.")
48         flagDial      = flag.String("dial", "", "optional ip:port to dial, to connect to a host:port but use a different SNI name (including a SNI name without DNS)")
49 )
50
51 type command struct {
52         run func(*h2i, []string) error // required
53
54         // complete optionally specifies tokens (case-insensitive) which are
55         // valid for this subcommand.
56         complete func() []string
57 }
58
59 var commands = map[string]command{
60         "ping": {run: (*h2i).cmdPing},
61         "settings": {
62                 run: (*h2i).cmdSettings,
63                 complete: func() []string {
64                         return []string{
65                                 "ACK",
66                                 http2.SettingHeaderTableSize.String(),
67                                 http2.SettingEnablePush.String(),
68                                 http2.SettingMaxConcurrentStreams.String(),
69                                 http2.SettingInitialWindowSize.String(),
70                                 http2.SettingMaxFrameSize.String(),
71                                 http2.SettingMaxHeaderListSize.String(),
72                         }
73                 },
74         },
75         "quit":    {run: (*h2i).cmdQuit},
76         "headers": {run: (*h2i).cmdHeaders},
77 }
78
79 func usage() {
80         fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
81         flag.PrintDefaults()
82 }
83
84 // withPort adds ":443" if another port isn't already present.
85 func withPort(host string) string {
86         if _, _, err := net.SplitHostPort(host); err != nil {
87                 return net.JoinHostPort(host, "443")
88         }
89         return host
90 }
91
92 // withoutPort strips the port from addr if present.
93 func withoutPort(addr string) string {
94         if h, _, err := net.SplitHostPort(addr); err == nil {
95                 return h
96         }
97         return addr
98 }
99
100 // h2i is the app's state.
101 type h2i struct {
102         host   string
103         tc     *tls.Conn
104         framer *http2.Framer
105         term   *terminal.Terminal
106
107         // owned by the command loop:
108         streamID uint32
109         hbuf     bytes.Buffer
110         henc     *hpack.Encoder
111
112         // owned by the readFrames loop:
113         peerSetting map[http2.SettingID]uint32
114         hdec        *hpack.Decoder
115 }
116
117 func main() {
118         flag.Usage = usage
119         flag.Parse()
120         if flag.NArg() != 1 {
121                 usage()
122                 os.Exit(2)
123         }
124         log.SetFlags(0)
125
126         host := flag.Arg(0)
127         app := &h2i{
128                 host:        host,
129                 peerSetting: make(map[http2.SettingID]uint32),
130         }
131         app.henc = hpack.NewEncoder(&app.hbuf)
132
133         if err := app.Main(); err != nil {
134                 if app.term != nil {
135                         app.logf("%v\n", err)
136                 } else {
137                         fmt.Fprintf(os.Stderr, "%v\n", err)
138                 }
139                 os.Exit(1)
140         }
141         fmt.Fprintf(os.Stdout, "\n")
142 }
143
144 func (app *h2i) Main() error {
145         cfg := &tls.Config{
146                 ServerName:         withoutPort(app.host),
147                 NextProtos:         strings.Split(*flagNextProto, ","),
148                 InsecureSkipVerify: *flagInsecure,
149         }
150
151         hostAndPort := *flagDial
152         if hostAndPort == "" {
153                 hostAndPort = withPort(app.host)
154         }
155         log.Printf("Connecting to %s ...", hostAndPort)
156         tc, err := tls.Dial("tcp", hostAndPort, cfg)
157         if err != nil {
158                 return fmt.Errorf("Error dialing %s: %v", hostAndPort, err)
159         }
160         log.Printf("Connected to %v", tc.RemoteAddr())
161         defer tc.Close()
162
163         if err := tc.Handshake(); err != nil {
164                 return fmt.Errorf("TLS handshake: %v", err)
165         }
166         if !*flagInsecure {
167                 if err := tc.VerifyHostname(app.host); err != nil {
168                         return fmt.Errorf("VerifyHostname: %v", err)
169                 }
170         }
171         state := tc.ConnectionState()
172         log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
173         if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
174                 return fmt.Errorf("Could not negotiate protocol mutually")
175         }
176
177         if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
178                 return err
179         }
180
181         app.framer = http2.NewFramer(tc, tc)
182
183         oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
184         if err != nil {
185                 return err
186         }
187         defer terminal.Restore(0, oldState)
188
189         var screen = struct {
190                 io.Reader
191                 io.Writer
192         }{os.Stdin, os.Stdout}
193
194         app.term = terminal.NewTerminal(screen, "h2i> ")
195         lastWord := regexp.MustCompile(`.+\W(\w+)$`)
196         app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
197                 if key != '\t' {
198                         return
199                 }
200                 if pos != len(line) {
201                         // TODO: we're being lazy for now, only supporting tab completion at the end.
202                         return
203                 }
204                 // Auto-complete for the command itself.
205                 if !strings.Contains(line, " ") {
206                         var name string
207                         name, _, ok = lookupCommand(line)
208                         if !ok {
209                                 return
210                         }
211                         return name, len(name), true
212                 }
213                 _, c, ok := lookupCommand(line[:strings.IndexByte(line, ' ')])
214                 if !ok || c.complete == nil {
215                         return
216                 }
217                 if strings.HasSuffix(line, " ") {
218                         app.logf("%s", strings.Join(c.complete(), " "))
219                         return line, pos, true
220                 }
221                 m := lastWord.FindStringSubmatch(line)
222                 if m == nil {
223                         return line, len(line), true
224                 }
225                 soFar := m[1]
226                 var match []string
227                 for _, cand := range c.complete() {
228                         if len(soFar) > len(cand) || !strings.EqualFold(cand[:len(soFar)], soFar) {
229                                 continue
230                         }
231                         match = append(match, cand)
232                 }
233                 if len(match) == 0 {
234                         return
235                 }
236                 if len(match) > 1 {
237                         // TODO: auto-complete any common prefix
238                         app.logf("%s", strings.Join(match, " "))
239                         return line, pos, true
240                 }
241                 newLine = line[:len(line)-len(soFar)] + match[0]
242                 return newLine, len(newLine), true
243
244         }
245
246         errc := make(chan error, 2)
247         go func() { errc <- app.readFrames() }()
248         go func() { errc <- app.readConsole() }()
249         return <-errc
250 }
251
252 func (app *h2i) logf(format string, args ...interface{}) {
253         fmt.Fprintf(app.term, format+"\r\n", args...)
254 }
255
256 func (app *h2i) readConsole() error {
257         if s := *flagSettings; s != "omit" {
258                 var args []string
259                 if s != "empty" {
260                         args = strings.Split(s, ",")
261                 }
262                 _, c, ok := lookupCommand("settings")
263                 if !ok {
264                         panic("settings command not found")
265                 }
266                 c.run(app, args)
267         }
268
269         for {
270                 line, err := app.term.ReadLine()
271                 if err == io.EOF {
272                         return nil
273                 }
274                 if err != nil {
275                         return fmt.Errorf("terminal.ReadLine: %v", err)
276                 }
277                 f := strings.Fields(line)
278                 if len(f) == 0 {
279                         continue
280                 }
281                 cmd, args := f[0], f[1:]
282                 if _, c, ok := lookupCommand(cmd); ok {
283                         err = c.run(app, args)
284                 } else {
285                         app.logf("Unknown command %q", line)
286                 }
287                 if err == errExitApp {
288                         return nil
289                 }
290                 if err != nil {
291                         return err
292                 }
293         }
294 }
295
296 func lookupCommand(prefix string) (name string, c command, ok bool) {
297         prefix = strings.ToLower(prefix)
298         if c, ok = commands[prefix]; ok {
299                 return prefix, c, ok
300         }
301
302         for full, candidate := range commands {
303                 if strings.HasPrefix(full, prefix) {
304                         if c.run != nil {
305                                 return "", command{}, false // ambiguous
306                         }
307                         c = candidate
308                         name = full
309                 }
310         }
311         return name, c, c.run != nil
312 }
313
314 var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
315
316 func (a *h2i) cmdQuit(args []string) error {
317         if len(args) > 0 {
318                 a.logf("the QUIT command takes no argument")
319                 return nil
320         }
321         return errExitApp
322 }
323
324 func (a *h2i) cmdSettings(args []string) error {
325         if len(args) == 1 && strings.EqualFold(args[0], "ACK") {
326                 return a.framer.WriteSettingsAck()
327         }
328         var settings []http2.Setting
329         for _, arg := range args {
330                 if strings.EqualFold(arg, "ACK") {
331                         a.logf("Error: ACK must be only argument with the SETTINGS command")
332                         return nil
333                 }
334                 eq := strings.Index(arg, "=")
335                 if eq == -1 {
336                         a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
337                         return nil
338                 }
339                 sid, ok := settingByName(arg[:eq])
340                 if !ok {
341                         a.logf("Error: unknown setting name %q", arg[:eq])
342                         return nil
343                 }
344                 val, err := strconv.ParseUint(arg[eq+1:], 10, 32)
345                 if err != nil {
346                         a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
347                         return nil
348                 }
349                 settings = append(settings, http2.Setting{
350                         ID:  sid,
351                         Val: uint32(val),
352                 })
353         }
354         a.logf("Sending: %v", settings)
355         return a.framer.WriteSettings(settings...)
356 }
357
358 func settingByName(name string) (http2.SettingID, bool) {
359         for _, sid := range [...]http2.SettingID{
360                 http2.SettingHeaderTableSize,
361                 http2.SettingEnablePush,
362                 http2.SettingMaxConcurrentStreams,
363                 http2.SettingInitialWindowSize,
364                 http2.SettingMaxFrameSize,
365                 http2.SettingMaxHeaderListSize,
366         } {
367                 if strings.EqualFold(sid.String(), name) {
368                         return sid, true
369                 }
370         }
371         return 0, false
372 }
373
374 func (app *h2i) cmdPing(args []string) error {
375         if len(args) > 1 {
376                 app.logf("invalid PING usage: only accepts 0 or 1 args")
377                 return nil // nil means don't end the program
378         }
379         var data [8]byte
380         if len(args) == 1 {
381                 copy(data[:], args[0])
382         } else {
383                 copy(data[:], "h2i_ping")
384         }
385         return app.framer.WritePing(false, data)
386 }
387
388 func (app *h2i) cmdHeaders(args []string) error {
389         if len(args) > 0 {
390                 app.logf("Error: HEADERS doesn't yet take arguments.")
391                 // TODO: flags for restricting window size, to force CONTINUATION
392                 // frames.
393                 return nil
394         }
395         var h1req bytes.Buffer
396         app.term.SetPrompt("(as HTTP/1.1)> ")
397         defer app.term.SetPrompt("h2i> ")
398         for {
399                 line, err := app.term.ReadLine()
400                 if err != nil {
401                         return err
402                 }
403                 h1req.WriteString(line)
404                 h1req.WriteString("\r\n")
405                 if line == "" {
406                         break
407                 }
408         }
409         req, err := http.ReadRequest(bufio.NewReader(&h1req))
410         if err != nil {
411                 app.logf("Invalid HTTP/1.1 request: %v", err)
412                 return nil
413         }
414         if app.streamID == 0 {
415                 app.streamID = 1
416         } else {
417                 app.streamID += 2
418         }
419         app.logf("Opening Stream-ID %d:", app.streamID)
420         hbf := app.encodeHeaders(req)
421         if len(hbf) > 16<<10 {
422                 app.logf("TODO: h2i doesn't yet write CONTINUATION frames. Copy it from transport.go")
423                 return nil
424         }
425         return app.framer.WriteHeaders(http2.HeadersFrameParam{
426                 StreamID:      app.streamID,
427                 BlockFragment: hbf,
428                 EndStream:     req.Method == "GET" || req.Method == "HEAD", // good enough for now
429                 EndHeaders:    true,                                        // for now
430         })
431 }
432
433 func (app *h2i) readFrames() error {
434         for {
435                 f, err := app.framer.ReadFrame()
436                 if err != nil {
437                         return fmt.Errorf("ReadFrame: %v", err)
438                 }
439                 app.logf("%v", f)
440                 switch f := f.(type) {
441                 case *http2.PingFrame:
442                         app.logf("  Data = %q", f.Data)
443                 case *http2.SettingsFrame:
444                         f.ForeachSetting(func(s http2.Setting) error {
445                                 app.logf("  %v", s)
446                                 app.peerSetting[s.ID] = s.Val
447                                 return nil
448                         })
449                 case *http2.WindowUpdateFrame:
450                         app.logf("  Window-Increment = %v", f.Increment)
451                 case *http2.GoAwayFrame:
452                         app.logf("  Last-Stream-ID = %d; Error-Code = %v (%d)", f.LastStreamID, f.ErrCode, f.ErrCode)
453                 case *http2.DataFrame:
454                         app.logf("  %q", f.Data())
455                 case *http2.HeadersFrame:
456                         if f.HasPriority() {
457                                 app.logf("  PRIORITY = %v", f.Priority)
458                         }
459                         if app.hdec == nil {
460                                 // TODO: if the user uses h2i to send a SETTINGS frame advertising
461                                 // something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE
462                                 // and stuff here instead of using the 4k default. But for now:
463                                 tableSize := uint32(4 << 10)
464                                 app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
465                         }
466                         app.hdec.Write(f.HeaderBlockFragment())
467                 case *http2.PushPromiseFrame:
468                         if app.hdec == nil {
469                                 // TODO: if the user uses h2i to send a SETTINGS frame advertising
470                                 // something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE
471                                 // and stuff here instead of using the 4k default. But for now:
472                                 tableSize := uint32(4 << 10)
473                                 app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
474                         }
475                         app.hdec.Write(f.HeaderBlockFragment())
476                 }
477         }
478 }
479
480 // called from readLoop
481 func (app *h2i) onNewHeaderField(f hpack.HeaderField) {
482         if f.Sensitive {
483                 app.logf("  %s = %q (SENSITIVE)", f.Name, f.Value)
484         }
485         app.logf("  %s = %q", f.Name, f.Value)
486 }
487
488 func (app *h2i) encodeHeaders(req *http.Request) []byte {
489         app.hbuf.Reset()
490
491         // TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
492         host := req.Host
493         if host == "" {
494                 host = req.URL.Host
495         }
496
497         path := req.RequestURI
498         if path == "" {
499                 path = "/"
500         }
501
502         app.writeHeader(":authority", host) // probably not right for all sites
503         app.writeHeader(":method", req.Method)
504         app.writeHeader(":path", path)
505         app.writeHeader(":scheme", "https")
506
507         for k, vv := range req.Header {
508                 lowKey := strings.ToLower(k)
509                 if lowKey == "host" {
510                         continue
511                 }
512                 for _, v := range vv {
513                         app.writeHeader(lowKey, v)
514                 }
515         }
516         return app.hbuf.Bytes()
517 }
518
519 func (app *h2i) writeHeader(name, value string) {
520         app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
521         app.logf(" %s = %s", name, value)
522 }