h2i.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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. // See https://code.google.com/p/go/source/browse/CONTRIBUTORS
  5. // Licensed under the same terms as Go itself:
  6. // https://code.google.com/p/go/source/browse/LICENSE
  7. /*
  8. The h2i command is an interactive HTTP/2 console.
  9. Usage:
  10. $ h2i [flags] <hostname>
  11. Interactive commands in the console:
  12. ping [data]
  13. settings ack
  14. */
  15. package main
  16. import (
  17. "bufio"
  18. "bytes"
  19. "crypto/tls"
  20. "errors"
  21. "flag"
  22. "fmt"
  23. "io"
  24. "log"
  25. "net"
  26. "net/http"
  27. "os"
  28. "strings"
  29. "github.com/bradfitz/http2"
  30. "github.com/bradfitz/http2/hpack"
  31. "golang.org/x/crypto/ssh/terminal"
  32. )
  33. // Flags
  34. var (
  35. flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
  36. flagInsecure = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
  37. )
  38. type command func(*h2i, []string) error
  39. var commands = map[string]command{
  40. "ping": (*h2i).cmdPing,
  41. "settings": (*h2i).cmdSettings,
  42. "quit": (*h2i).cmdQuit,
  43. "headers": (*h2i).cmdHeaders,
  44. }
  45. func usage() {
  46. fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
  47. flag.PrintDefaults()
  48. os.Exit(1)
  49. }
  50. // withPort adds ":443" if another port isn't already present.
  51. func withPort(host string) string {
  52. if _, _, err := net.SplitHostPort(host); err != nil {
  53. return net.JoinHostPort(host, "443")
  54. }
  55. return host
  56. }
  57. // h2i is the app's state.
  58. type h2i struct {
  59. host string
  60. tc *tls.Conn
  61. framer *http2.Framer
  62. term *terminal.Terminal
  63. // owned by the command loop:
  64. streamID uint32
  65. hbuf bytes.Buffer
  66. henc *hpack.Encoder
  67. // owned by the readFrames loop:
  68. peerSetting map[http2.SettingID]uint32
  69. hdec *hpack.Decoder
  70. }
  71. func main() {
  72. flag.Usage = usage
  73. flag.Parse()
  74. if flag.NArg() != 1 {
  75. usage()
  76. }
  77. log.SetFlags(0)
  78. host := flag.Arg(0)
  79. app := &h2i{
  80. host: host,
  81. peerSetting: make(map[http2.SettingID]uint32),
  82. }
  83. app.henc = hpack.NewEncoder(&app.hbuf)
  84. if err := app.Main(); err != nil {
  85. if app.term != nil {
  86. app.logf("%v\n", err)
  87. } else {
  88. fmt.Fprintf(os.Stderr, "%v\n", err)
  89. }
  90. os.Exit(1)
  91. }
  92. }
  93. func (app *h2i) Main() error {
  94. cfg := &tls.Config{
  95. ServerName: app.host,
  96. NextProtos: strings.Split(*flagNextProto, ","),
  97. InsecureSkipVerify: *flagInsecure,
  98. }
  99. hostAndPort := withPort(app.host)
  100. log.Printf("Connecting to %s ...", hostAndPort)
  101. tc, err := tls.Dial("tcp", hostAndPort, cfg)
  102. if err != nil {
  103. return fmt.Errorf("Error dialing %s: %v", withPort(app.host), err)
  104. }
  105. log.Printf("Connected to %v", tc.RemoteAddr())
  106. defer tc.Close()
  107. if err := tc.Handshake(); err != nil {
  108. return fmt.Errorf("TLS handshake: %v", err)
  109. }
  110. if !*flagInsecure {
  111. if err := tc.VerifyHostname(app.host); err != nil {
  112. return fmt.Errorf("VerifyHostname: %v", err)
  113. }
  114. }
  115. state := tc.ConnectionState()
  116. log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
  117. if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
  118. return fmt.Errorf("Could not negotiate protocol mutually")
  119. }
  120. if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
  121. return err
  122. }
  123. app.framer = http2.NewFramer(tc, tc)
  124. oldState, err := terminal.MakeRaw(0)
  125. if err != nil {
  126. return err
  127. }
  128. defer terminal.Restore(0, oldState)
  129. var screen = struct {
  130. io.Reader
  131. io.Writer
  132. }{os.Stdin, os.Stdout}
  133. app.term = terminal.NewTerminal(screen, "h2i> ")
  134. app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
  135. if key != '\t' {
  136. return
  137. }
  138. name, _, ok := lookupCommand(line)
  139. if !ok {
  140. return
  141. }
  142. return name, len(name), true
  143. }
  144. errc := make(chan error, 2)
  145. go func() { errc <- app.readFrames() }()
  146. go func() { errc <- app.readConsole() }()
  147. return <-errc
  148. }
  149. func (app *h2i) logf(format string, args ...interface{}) {
  150. fmt.Fprintf(app.term, format+"\n", args...)
  151. }
  152. func (app *h2i) readConsole() error {
  153. for {
  154. line, err := app.term.ReadLine()
  155. if err != nil {
  156. return fmt.Errorf("terminal.ReadLine: %v", err)
  157. }
  158. f := strings.Fields(line)
  159. if len(f) == 0 {
  160. continue
  161. }
  162. cmd, args := f[0], f[1:]
  163. if _, fn, ok := lookupCommand(cmd); ok {
  164. err = fn(app, args)
  165. } else {
  166. app.logf("Unknown command %q", line)
  167. }
  168. if err == errExitApp {
  169. return nil
  170. }
  171. if err != nil {
  172. return err
  173. }
  174. }
  175. }
  176. func lookupCommand(prefix string) (name string, c command, ok bool) {
  177. prefix = strings.ToLower(prefix)
  178. if c, ok = commands[prefix]; ok {
  179. return prefix, c, ok
  180. }
  181. for full, candidate := range commands {
  182. if strings.HasPrefix(full, prefix) {
  183. if c != nil {
  184. return "", nil, false // ambiguous
  185. }
  186. c = candidate
  187. name = full
  188. }
  189. }
  190. return name, c, c != nil
  191. }
  192. var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
  193. func (app *h2i) cmdQuit(args []string) error {
  194. if len(args) > 0 {
  195. app.logf("the QUIT command takes no argument")
  196. return nil
  197. }
  198. return errExitApp
  199. }
  200. func (app *h2i) cmdSettings(args []string) error {
  201. if len(args) == 1 && args[0] == "ack" {
  202. return app.framer.WriteSettingsAck()
  203. }
  204. app.logf("TODO: unhandled SETTINGS")
  205. return nil
  206. }
  207. func (app *h2i) cmdPing(args []string) error {
  208. if len(args) > 1 {
  209. app.logf("invalid PING usage: only accepts 0 or 1 args")
  210. return nil // nil means don't end the program
  211. }
  212. var data [8]byte
  213. if len(args) == 1 {
  214. copy(data[:], args[0])
  215. } else {
  216. copy(data[:], "h2i_ping")
  217. }
  218. return app.framer.WritePing(false, data)
  219. }
  220. func (app *h2i) cmdHeaders(args []string) error {
  221. if len(args) > 0 {
  222. app.logf("Error: HEADERS doesn't yet take arguments.")
  223. // TODO: flags for restricting window size, to force CONTINUATION
  224. // frames.
  225. return nil
  226. }
  227. var h1req bytes.Buffer
  228. app.term.SetPrompt("(as HTTP/1.1)> ")
  229. defer app.term.SetPrompt("h2i> ")
  230. for {
  231. line, err := app.term.ReadLine()
  232. if err != nil {
  233. return err
  234. }
  235. h1req.WriteString(line)
  236. h1req.WriteString("\r\n")
  237. if line == "" {
  238. break
  239. }
  240. }
  241. req, err := http.ReadRequest(bufio.NewReader(&h1req))
  242. if err != nil {
  243. app.logf("Invalid HTTP/1.1 request: %v", err)
  244. return nil
  245. }
  246. if app.streamID == 0 {
  247. app.streamID = 1
  248. } else {
  249. app.streamID += 2
  250. }
  251. app.logf("Opening Stream-ID %d:", app.streamID)
  252. hbf := app.encodeHeaders(req)
  253. if len(hbf) > 16<<10 {
  254. app.logf("TODO: h2i doesn't yet write CONTINUATION frames. Copy it from transport.go")
  255. return nil
  256. }
  257. return app.framer.WriteHeaders(http2.HeadersFrameParam{
  258. StreamID: app.streamID,
  259. BlockFragment: hbf,
  260. EndStream: req.Method == "GET" || req.Method == "HEAD", // good enough for now
  261. EndHeaders: true, // for now
  262. })
  263. }
  264. func (app *h2i) readFrames() error {
  265. for {
  266. f, err := app.framer.ReadFrame()
  267. if err != nil {
  268. return fmt.Errorf("ReadFrame: %v", err)
  269. }
  270. app.logf("%v", f)
  271. switch f := f.(type) {
  272. case *http2.PingFrame:
  273. app.logf(" Data = %q", f.Data)
  274. case *http2.SettingsFrame:
  275. f.ForeachSetting(func(s http2.Setting) error {
  276. app.logf(" %v", s)
  277. app.peerSetting[s.ID] = s.Val
  278. return nil
  279. })
  280. case *http2.WindowUpdateFrame:
  281. app.logf(" Window-Increment = %v\n", f.Increment)
  282. case *http2.GoAwayFrame:
  283. app.logf(" Last-Stream-ID = %d; Error-Code = %v (%d)\n", f.LastStreamID, f.ErrCode, f.ErrCode)
  284. case *http2.DataFrame:
  285. app.logf(" %q", f.Data())
  286. case *http2.HeadersFrame:
  287. if f.HasPriority() {
  288. app.logf(" PRIORITY = %v", f.Priority)
  289. }
  290. if app.hdec == nil {
  291. // TODO: if the user uses h2i to send a SETTINGS frame advertising
  292. // something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE
  293. // and stuff here instead of using the 4k default. But for now:
  294. tableSize := uint32(4 << 10)
  295. app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
  296. }
  297. app.hdec.Write(f.HeaderBlockFragment())
  298. }
  299. }
  300. }
  301. // called from readLoop
  302. func (app *h2i) onNewHeaderField(f hpack.HeaderField) {
  303. if f.Sensitive {
  304. app.logf(" %s = %q (SENSITIVE)", f.Name, f.Value)
  305. }
  306. app.logf(" %s = %q", f.Name, f.Value)
  307. }
  308. func (app *h2i) encodeHeaders(req *http.Request) []byte {
  309. app.hbuf.Reset()
  310. // TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
  311. host := req.Host
  312. if host == "" {
  313. host = req.URL.Host
  314. }
  315. path := req.URL.Path
  316. if path == "" {
  317. path = "/"
  318. }
  319. app.writeHeader(":authority", host) // probably not right for all sites
  320. app.writeHeader(":method", req.Method)
  321. app.writeHeader(":path", path)
  322. app.writeHeader(":scheme", "https")
  323. for k, vv := range req.Header {
  324. lowKey := strings.ToLower(k)
  325. if lowKey == "host" {
  326. continue
  327. }
  328. for _, v := range vv {
  329. app.writeHeader(lowKey, v)
  330. }
  331. }
  332. return app.hbuf.Bytes()
  333. }
  334. func (app *h2i) writeHeader(name, value string) {
  335. app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
  336. app.logf(" %s = %s", name, value)
  337. }