h2i.go 12 KB

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