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.

652 lines
19 KiB

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