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