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