h2i.go 12 KB

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