You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

510 lines
12 KiB

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