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  }