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.

649 lines
19 KiB

8 years ago
8 years ago
8 years ago
  1. package rpcserver
  2. import (
  3. "bytes"
  4. "encoding/hex"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "reflect"
  10. "sort"
  11. "strings"
  12. "time"
  13. "github.com/gorilla/websocket"
  14. "github.com/pkg/errors"
  15. types "github.com/tendermint/go-rpc/types"
  16. wire "github.com/tendermint/go-wire"
  17. cmn "github.com/tendermint/tmlibs/common"
  18. events "github.com/tendermint/tmlibs/events"
  19. )
  20. // Adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
  21. // "result" is the interface on which the result objects are registered, and is popualted with every RPCResponse
  22. func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc) {
  23. // HTTP endpoints
  24. for funcName, rpcFunc := range funcMap {
  25. mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc))
  26. }
  27. // JSONRPC endpoints
  28. mux.HandleFunc("/", makeJSONRPCHandler(funcMap))
  29. }
  30. //-------------------------------------
  31. // function introspection
  32. // holds all type information for each function
  33. type RPCFunc struct {
  34. f reflect.Value // underlying rpc function
  35. args []reflect.Type // type of each function arg
  36. returns []reflect.Type // type of each return arg
  37. argNames []string // name of each argument
  38. ws bool // websocket only
  39. }
  40. // wraps a function for quicker introspection
  41. // f is the function, args are comma separated argument names
  42. func NewRPCFunc(f interface{}, args string) *RPCFunc {
  43. return newRPCFunc(f, args, false)
  44. }
  45. func NewWSRPCFunc(f interface{}, args string) *RPCFunc {
  46. return newRPCFunc(f, args, true)
  47. }
  48. func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc {
  49. var argNames []string
  50. if args != "" {
  51. argNames = strings.Split(args, ",")
  52. }
  53. return &RPCFunc{
  54. f: reflect.ValueOf(f),
  55. args: funcArgTypes(f),
  56. returns: funcReturnTypes(f),
  57. argNames: argNames,
  58. ws: ws,
  59. }
  60. }
  61. // return a function's argument types
  62. func funcArgTypes(f interface{}) []reflect.Type {
  63. t := reflect.TypeOf(f)
  64. n := t.NumIn()
  65. typez := make([]reflect.Type, n)
  66. for i := 0; i < n; i++ {
  67. typez[i] = t.In(i)
  68. }
  69. return typez
  70. }
  71. // return a function's return types
  72. func funcReturnTypes(f interface{}) []reflect.Type {
  73. t := reflect.TypeOf(f)
  74. n := t.NumOut()
  75. typez := make([]reflect.Type, n)
  76. for i := 0; i < n; i++ {
  77. typez[i] = t.Out(i)
  78. }
  79. return typez
  80. }
  81. // function introspection
  82. //-----------------------------------------------------------------------------
  83. // rpc.json
  84. // jsonrpc calls grab the given method's function info and runs reflect.Call
  85. func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc {
  86. return func(w http.ResponseWriter, r *http.Request) {
  87. b, _ := ioutil.ReadAll(r.Body)
  88. // if its an empty request (like from a browser),
  89. // just display a list of functions
  90. if len(b) == 0 {
  91. writeListOfEndpoints(w, r, funcMap)
  92. return
  93. }
  94. var request types.RPCRequest
  95. err := json.Unmarshal(b, &request)
  96. if err != nil {
  97. WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error())))
  98. return
  99. }
  100. if len(r.URL.Path) > 1 {
  101. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path)))
  102. return
  103. }
  104. rpcFunc := funcMap[request.Method]
  105. if rpcFunc == nil {
  106. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
  107. return
  108. }
  109. if rpcFunc.ws {
  110. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method))
  111. return
  112. }
  113. args, err := jsonParamsToArgsRPC(rpcFunc, request.Params)
  114. if err != nil {
  115. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error())))
  116. return
  117. }
  118. returns := rpcFunc.f.Call(args)
  119. log.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
  120. result, err := unreflectResult(returns)
  121. if err != nil {
  122. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, err.Error()))
  123. return
  124. }
  125. WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, ""))
  126. }
  127. }
  128. // Convert a []interface{} OR a map[string]interface{} to properly typed values
  129. //
  130. // argsOffset should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames).
  131. // Example:
  132. // rpcFunc.args = [rpctypes.WSRPCContext string]
  133. // rpcFunc.argNames = ["arg"]
  134. func jsonParamsToArgs(rpcFunc *RPCFunc, paramsI interface{}, argsOffset int) ([]reflect.Value, error) {
  135. values := make([]reflect.Value, len(rpcFunc.argNames))
  136. switch params := paramsI.(type) {
  137. case map[string]interface{}:
  138. for i, argName := range rpcFunc.argNames {
  139. argType := rpcFunc.args[i+argsOffset]
  140. // decode param if provided
  141. if param, ok := params[argName]; ok && "" != param {
  142. v, err := _jsonObjectToArg(argType, param)
  143. if err != nil {
  144. return nil, err
  145. }
  146. values[i] = v
  147. } else { // use default for that type
  148. values[i] = reflect.Zero(argType)
  149. }
  150. }
  151. case []interface{}:
  152. if len(rpcFunc.argNames) != len(params) {
  153. return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
  154. len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
  155. }
  156. values := make([]reflect.Value, len(params))
  157. for i, p := range params {
  158. ty := rpcFunc.args[i+argsOffset]
  159. v, err := _jsonObjectToArg(ty, p)
  160. if err != nil {
  161. return nil, err
  162. }
  163. values[i] = v
  164. }
  165. return values, nil
  166. default:
  167. return nil, fmt.Errorf("Unknown type for JSON params %v. Expected map[string]interface{} or []interface{}", reflect.TypeOf(paramsI))
  168. }
  169. return values, nil
  170. }
  171. // Convert a []interface{} OR a map[string]interface{} to properly typed values
  172. func jsonParamsToArgsRPC(rpcFunc *RPCFunc, paramsI interface{}) ([]reflect.Value, error) {
  173. return jsonParamsToArgs(rpcFunc, paramsI, 0)
  174. }
  175. // Same as above, but with the first param the websocket connection
  176. func jsonParamsToArgsWS(rpcFunc *RPCFunc, paramsI interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) {
  177. values, err := jsonParamsToArgs(rpcFunc, paramsI, 1)
  178. if err != nil {
  179. return nil, err
  180. }
  181. return append([]reflect.Value{reflect.ValueOf(wsCtx)}, values...), nil
  182. }
  183. func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
  184. var err error
  185. v := reflect.New(ty)
  186. wire.ReadJSONObjectPtr(v.Interface(), object, &err)
  187. if err != nil {
  188. return v, err
  189. }
  190. v = v.Elem()
  191. return v, nil
  192. }
  193. // rpc.json
  194. //-----------------------------------------------------------------------------
  195. // rpc.http
  196. // convert from a function name to the http handler
  197. func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request) {
  198. // Exception for websocket endpoints
  199. if rpcFunc.ws {
  200. return func(w http.ResponseWriter, r *http.Request) {
  201. WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, "This RPC method is only for websockets"))
  202. }
  203. }
  204. // All other endpoints
  205. return func(w http.ResponseWriter, r *http.Request) {
  206. log.Debug("HTTP HANDLER", "req", r)
  207. args, err := httpParamsToArgs(rpcFunc, r)
  208. if err != nil {
  209. WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error())))
  210. return
  211. }
  212. returns := rpcFunc.f.Call(args)
  213. log.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
  214. result, err := unreflectResult(returns)
  215. if err != nil {
  216. WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, err.Error()))
  217. return
  218. }
  219. WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, ""))
  220. }
  221. }
  222. // Covert an http query to a list of properly typed values.
  223. // To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
  224. func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
  225. values := make([]reflect.Value, len(rpcFunc.args))
  226. for i, name := range rpcFunc.argNames {
  227. argType := rpcFunc.args[i]
  228. values[i] = reflect.Zero(argType) // set default for that type
  229. arg := GetParam(r, name)
  230. // log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)
  231. if "" == arg {
  232. continue
  233. }
  234. v, err, ok := nonJsonToArg(argType, arg)
  235. if err != nil {
  236. return nil, err
  237. }
  238. if ok {
  239. values[i] = v
  240. continue
  241. }
  242. // Pass values to go-wire
  243. values[i], err = _jsonStringToArg(argType, arg)
  244. if err != nil {
  245. return nil, err
  246. }
  247. }
  248. return values, nil
  249. }
  250. func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) {
  251. var err error
  252. v := reflect.New(ty)
  253. wire.ReadJSONPtr(v.Interface(), []byte(arg), &err)
  254. if err != nil {
  255. return v, err
  256. }
  257. v = v.Elem()
  258. return v, nil
  259. }
  260. func nonJsonToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) {
  261. isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`)
  262. isHexString := strings.HasPrefix(strings.ToLower(arg), "0x")
  263. expectingString := ty.Kind() == reflect.String
  264. expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8
  265. if isHexString {
  266. if !expectingString && !expectingByteSlice {
  267. err := errors.Errorf("Got a hex string arg, but expected '%s'",
  268. ty.Kind().String())
  269. return reflect.ValueOf(nil), err, false
  270. }
  271. var value []byte
  272. value, err := hex.DecodeString(arg[2:])
  273. if err != nil {
  274. return reflect.ValueOf(nil), err, false
  275. }
  276. if ty.Kind() == reflect.String {
  277. return reflect.ValueOf(string(value)), nil, true
  278. }
  279. return reflect.ValueOf([]byte(value)), nil, true
  280. }
  281. if isQuotedString && expectingByteSlice {
  282. var err error
  283. v := reflect.New(reflect.TypeOf(""))
  284. wire.ReadJSONPtr(v.Interface(), []byte(arg), &err)
  285. if err != nil {
  286. return reflect.ValueOf(nil), err, false
  287. }
  288. v = v.Elem()
  289. return reflect.ValueOf([]byte(v.String())), nil, true
  290. }
  291. return reflect.ValueOf(nil), nil, false
  292. }
  293. // rpc.http
  294. //-----------------------------------------------------------------------------
  295. // rpc.websocket
  296. const (
  297. writeChanCapacity = 1000
  298. wsWriteTimeoutSeconds = 30 // each write times out after this
  299. wsReadTimeoutSeconds = 30 // connection times out if we haven't received *anything* in this long, not even pings.
  300. wsPingTickerSeconds = 10 // send a ping every PingTickerSeconds.
  301. )
  302. // a single websocket connection
  303. // contains listener id, underlying ws connection,
  304. // and the event switch for subscribing to events
  305. type wsConnection struct {
  306. cmn.BaseService
  307. remoteAddr string
  308. baseConn *websocket.Conn
  309. writeChan chan types.RPCResponse
  310. readTimeout *time.Timer
  311. pingTicker *time.Ticker
  312. funcMap map[string]*RPCFunc
  313. evsw events.EventSwitch
  314. }
  315. // new websocket connection wrapper
  316. func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw events.EventSwitch) *wsConnection {
  317. wsc := &wsConnection{
  318. remoteAddr: baseConn.RemoteAddr().String(),
  319. baseConn: baseConn,
  320. writeChan: make(chan types.RPCResponse, writeChanCapacity), // error when full.
  321. funcMap: funcMap,
  322. evsw: evsw,
  323. }
  324. wsc.BaseService = *cmn.NewBaseService(log, "wsConnection", wsc)
  325. return wsc
  326. }
  327. // wsc.Start() blocks until the connection closes.
  328. func (wsc *wsConnection) OnStart() error {
  329. wsc.BaseService.OnStart()
  330. // these must be set before the readRoutine is created, as it may
  331. // call wsc.Stop(), which accesses these timers
  332. wsc.readTimeout = time.NewTimer(time.Second * wsReadTimeoutSeconds)
  333. wsc.pingTicker = time.NewTicker(time.Second * wsPingTickerSeconds)
  334. // Read subscriptions/unsubscriptions to events
  335. go wsc.readRoutine()
  336. // Custom Ping handler to touch readTimeout
  337. wsc.baseConn.SetPingHandler(func(m string) error {
  338. // NOTE: https://github.com/gorilla/websocket/issues/97
  339. go wsc.baseConn.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
  340. wsc.readTimeout.Reset(time.Second * wsReadTimeoutSeconds)
  341. return nil
  342. })
  343. wsc.baseConn.SetPongHandler(func(m string) error {
  344. // NOTE: https://github.com/gorilla/websocket/issues/97
  345. wsc.readTimeout.Reset(time.Second * wsReadTimeoutSeconds)
  346. return nil
  347. })
  348. go wsc.readTimeoutRoutine()
  349. // Write responses, BLOCKING.
  350. wsc.writeRoutine()
  351. return nil
  352. }
  353. func (wsc *wsConnection) OnStop() {
  354. wsc.BaseService.OnStop()
  355. if wsc.evsw != nil {
  356. wsc.evsw.RemoveListener(wsc.remoteAddr)
  357. }
  358. wsc.readTimeout.Stop()
  359. wsc.pingTicker.Stop()
  360. // The write loop closes the websocket connection
  361. // when it exits its loop, and the read loop
  362. // closes the writeChan
  363. }
  364. func (wsc *wsConnection) readTimeoutRoutine() {
  365. select {
  366. case <-wsc.readTimeout.C:
  367. log.Notice("Stopping connection due to read timeout")
  368. wsc.Stop()
  369. case <-wsc.Quit:
  370. return
  371. }
  372. }
  373. // Implements WSRPCConnection
  374. func (wsc *wsConnection) GetRemoteAddr() string {
  375. return wsc.remoteAddr
  376. }
  377. // Implements WSRPCConnection
  378. func (wsc *wsConnection) GetEventSwitch() events.EventSwitch {
  379. return wsc.evsw
  380. }
  381. // Implements WSRPCConnection
  382. // Blocking write to writeChan until service stops.
  383. // Goroutine-safe
  384. func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) {
  385. select {
  386. case <-wsc.Quit:
  387. return
  388. case wsc.writeChan <- resp:
  389. }
  390. }
  391. // Implements WSRPCConnection
  392. // Nonblocking write.
  393. // Goroutine-safe
  394. func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool {
  395. select {
  396. case <-wsc.Quit:
  397. return false
  398. case wsc.writeChan <- resp:
  399. return true
  400. default:
  401. return false
  402. }
  403. }
  404. // Read from the socket and subscribe to or unsubscribe from events
  405. func (wsc *wsConnection) readRoutine() {
  406. // Do not close writeChan, to allow WriteRPCResponse() to fail.
  407. // defer close(wsc.writeChan)
  408. for {
  409. select {
  410. case <-wsc.Quit:
  411. return
  412. default:
  413. var in []byte
  414. // Do not set a deadline here like below:
  415. // wsc.baseConn.SetReadDeadline(time.Now().Add(time.Second * wsReadTimeoutSeconds))
  416. // The client may not send anything for a while.
  417. // We use `readTimeout` to handle read timeouts.
  418. _, in, err := wsc.baseConn.ReadMessage()
  419. if err != nil {
  420. log.Notice("Failed to read from connection", "remote", wsc.remoteAddr, "err", err.Error())
  421. // an error reading the connection,
  422. // kill the connection
  423. wsc.Stop()
  424. return
  425. }
  426. var request types.RPCRequest
  427. err = json.Unmarshal(in, &request)
  428. if err != nil {
  429. errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error())
  430. wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr))
  431. continue
  432. }
  433. // Now, fetch the RPCFunc and execute it.
  434. rpcFunc := wsc.funcMap[request.Method]
  435. if rpcFunc == nil {
  436. wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
  437. continue
  438. }
  439. var args []reflect.Value
  440. if rpcFunc.ws {
  441. wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
  442. args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx)
  443. } else {
  444. args, err = jsonParamsToArgsRPC(rpcFunc, request.Params)
  445. }
  446. if err != nil {
  447. wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
  448. continue
  449. }
  450. returns := rpcFunc.f.Call(args)
  451. log.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns)
  452. result, err := unreflectResult(returns)
  453. if err != nil {
  454. wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
  455. continue
  456. } else {
  457. wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, ""))
  458. continue
  459. }
  460. }
  461. }
  462. }
  463. // receives on a write channel and writes out on the socket
  464. func (wsc *wsConnection) writeRoutine() {
  465. defer wsc.baseConn.Close()
  466. for {
  467. select {
  468. case <-wsc.Quit:
  469. return
  470. case <-wsc.pingTicker.C:
  471. err := wsc.baseConn.WriteMessage(websocket.PingMessage, []byte{})
  472. if err != nil {
  473. log.Error("Failed to write ping message on websocket", "error", err)
  474. wsc.Stop()
  475. return
  476. }
  477. case msg := <-wsc.writeChan:
  478. jsonBytes, err := json.Marshal(msg)
  479. if err != nil {
  480. log.Error("Failed to marshal RPCResponse to JSON", "error", err)
  481. } else {
  482. wsc.baseConn.SetWriteDeadline(time.Now().Add(time.Second * wsWriteTimeoutSeconds))
  483. if err = wsc.baseConn.WriteMessage(websocket.TextMessage, jsonBytes); err != nil {
  484. log.Warn("Failed to write response on websocket", "error", err)
  485. wsc.Stop()
  486. return
  487. }
  488. }
  489. }
  490. }
  491. }
  492. //----------------------------------------
  493. // Main manager for all websocket connections
  494. // Holds the event switch
  495. // NOTE: The websocket path is defined externally, e.g. in node/node.go
  496. type WebsocketManager struct {
  497. websocket.Upgrader
  498. funcMap map[string]*RPCFunc
  499. evsw events.EventSwitch
  500. }
  501. func NewWebsocketManager(funcMap map[string]*RPCFunc, evsw events.EventSwitch) *WebsocketManager {
  502. return &WebsocketManager{
  503. funcMap: funcMap,
  504. evsw: evsw,
  505. Upgrader: websocket.Upgrader{
  506. ReadBufferSize: 1024,
  507. WriteBufferSize: 1024,
  508. CheckOrigin: func(r *http.Request) bool {
  509. // TODO
  510. return true
  511. },
  512. },
  513. }
  514. }
  515. // Upgrade the request/response (via http.Hijack) and starts the wsConnection.
  516. func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
  517. wsConn, err := wm.Upgrade(w, r, nil)
  518. if err != nil {
  519. // TODO - return http error
  520. log.Error("Failed to upgrade to websocket connection", "error", err)
  521. return
  522. }
  523. // register connection
  524. con := NewWSConnection(wsConn, wm.funcMap, wm.evsw)
  525. log.Notice("New websocket connection", "remote", con.remoteAddr)
  526. con.Start() // Blocking
  527. }
  528. // rpc.websocket
  529. //-----------------------------------------------------------------------------
  530. // NOTE: assume returns is result struct and error. If error is not nil, return it
  531. func unreflectResult(returns []reflect.Value) (interface{}, error) {
  532. errV := returns[1]
  533. if errV.Interface() != nil {
  534. return nil, errors.Errorf("%v", errV.Interface())
  535. }
  536. rv := returns[0]
  537. // the result is a registered interface,
  538. // we need a pointer to it so we can marshal with type byte
  539. rvp := reflect.New(rv.Type())
  540. rvp.Elem().Set(rv)
  541. return rvp.Interface(), nil
  542. }
  543. // writes a list of available rpc endpoints as an html page
  544. func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
  545. noArgNames := []string{}
  546. argNames := []string{}
  547. for name, funcData := range funcMap {
  548. if len(funcData.args) == 0 {
  549. noArgNames = append(noArgNames, name)
  550. } else {
  551. argNames = append(argNames, name)
  552. }
  553. }
  554. sort.Strings(noArgNames)
  555. sort.Strings(argNames)
  556. buf := new(bytes.Buffer)
  557. buf.WriteString("<html><body>")
  558. buf.WriteString("<br>Available endpoints:<br>")
  559. for _, name := range noArgNames {
  560. link := fmt.Sprintf("http://%s/%s", r.Host, name)
  561. buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
  562. }
  563. buf.WriteString("<br>Endpoints that require arguments:<br>")
  564. for _, name := range argNames {
  565. link := fmt.Sprintf("http://%s/%s?", r.Host, name)
  566. funcData := funcMap[name]
  567. for i, argName := range funcData.argNames {
  568. link += argName + "=_"
  569. if i < len(funcData.argNames)-1 {
  570. link += "&"
  571. }
  572. }
  573. buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
  574. }
  575. buf.WriteString("</body></html>")
  576. w.Header().Set("Content-Type", "text/html")
  577. w.WriteHeader(200)
  578. w.Write(buf.Bytes())
  579. }