Run Format

Source file src/pkg/net/rpc/server.go

     1	// Copyright 2009 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	/*
     6		Package rpc provides access to the exported methods of an object across a
     7		network or other I/O connection.  A server registers an object, making it visible
     8		as a service with the name of the type of the object.  After registration, exported
     9		methods of the object will be accessible remotely.  A server may register multiple
    10		objects (services) of different types but it is an error to register multiple
    11		objects of the same type.
    12	
    13		Only methods that satisfy these criteria will be made available for remote access;
    14		other methods will be ignored:
    15	
    16			- the method is exported.
    17			- the method has two arguments, both exported (or builtin) types.
    18			- the method's second argument is a pointer.
    19			- the method has return type error.
    20	
    21		In effect, the method must look schematically like
    22	
    23			func (t *T) MethodName(argType T1, replyType *T2) error
    24	
    25		where T, T1 and T2 can be marshaled by encoding/gob.
    26		These requirements apply even if a different codec is used.
    27		(In the future, these requirements may soften for custom codecs.)
    28	
    29		The method's first argument represents the arguments provided by the caller; the
    30		second argument represents the result parameters to be returned to the caller.
    31		The method's return value, if non-nil, is passed back as a string that the client
    32		sees as if created by errors.New.  If an error is returned, the reply parameter
    33		will not be sent back to the client.
    34	
    35		The server may handle requests on a single connection by calling ServeConn.  More
    36		typically it will create a network listener and call Accept or, for an HTTP
    37		listener, HandleHTTP and http.Serve.
    38	
    39		A client wishing to use the service establishes a connection and then invokes
    40		NewClient on the connection.  The convenience function Dial (DialHTTP) performs
    41		both steps for a raw network connection (an HTTP connection).  The resulting
    42		Client object has two methods, Call and Go, that specify the service and method to
    43		call, a pointer containing the arguments, and a pointer to receive the result
    44		parameters.
    45	
    46		The Call method waits for the remote call to complete while the Go method
    47		launches the call asynchronously and signals completion using the Call
    48		structure's Done channel.
    49	
    50		Unless an explicit codec is set up, package encoding/gob is used to
    51		transport the data.
    52	
    53		Here is a simple example.  A server wishes to export an object of type Arith:
    54	
    55			package server
    56	
    57			type Args struct {
    58				A, B int
    59			}
    60	
    61			type Quotient struct {
    62				Quo, Rem int
    63			}
    64	
    65			type Arith int
    66	
    67			func (t *Arith) Multiply(args *Args, reply *int) error {
    68				*reply = args.A * args.B
    69				return nil
    70			}
    71	
    72			func (t *Arith) Divide(args *Args, quo *Quotient) error {
    73				if args.B == 0 {
    74					return errors.New("divide by zero")
    75				}
    76				quo.Quo = args.A / args.B
    77				quo.Rem = args.A % args.B
    78				return nil
    79			}
    80	
    81		The server calls (for HTTP service):
    82	
    83			arith := new(Arith)
    84			rpc.Register(arith)
    85			rpc.HandleHTTP()
    86			l, e := net.Listen("tcp", ":1234")
    87			if e != nil {
    88				log.Fatal("listen error:", e)
    89			}
    90			go http.Serve(l, nil)
    91	
    92		At this point, clients can see a service "Arith" with methods "Arith.Multiply" and
    93		"Arith.Divide".  To invoke one, a client first dials the server:
    94	
    95			client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
    96			if err != nil {
    97				log.Fatal("dialing:", err)
    98			}
    99	
   100		Then it can make a remote call:
   101	
   102			// Synchronous call
   103			args := &server.Args{7,8}
   104			var reply int
   105			err = client.Call("Arith.Multiply", args, &reply)
   106			if err != nil {
   107				log.Fatal("arith error:", err)
   108			}
   109			fmt.Printf("Arith: %d*%d=%d", args.A, args.B, reply)
   110	
   111		or
   112	
   113			// Asynchronous call
   114			quotient := new(Quotient)
   115			divCall := client.Go("Arith.Divide", args, quotient, nil)
   116			replyCall := <-divCall.Done	// will be equal to divCall
   117			// check errors, print, etc.
   118	
   119		A server implementation will often provide a simple, type-safe wrapper for the
   120		client.
   121	*/
   122	package rpc
   123	
   124	import (
   125		"bufio"
   126		"encoding/gob"
   127		"errors"
   128		"io"
   129		"log"
   130		"net"
   131		"net/http"
   132		"reflect"
   133		"strings"
   134		"sync"
   135		"unicode"
   136		"unicode/utf8"
   137	)
   138	
   139	const (
   140		// Defaults used by HandleHTTP
   141		DefaultRPCPath   = "/_goRPC_"
   142		DefaultDebugPath = "/debug/rpc"
   143	)
   144	
   145	// Precompute the reflect type for error.  Can't use error directly
   146	// because Typeof takes an empty interface value.  This is annoying.
   147	var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
   148	
   149	type methodType struct {
   150		sync.Mutex // protects counters
   151		method     reflect.Method
   152		ArgType    reflect.Type
   153		ReplyType  reflect.Type
   154		numCalls   uint
   155	}
   156	
   157	type service struct {
   158		name   string                 // name of service
   159		rcvr   reflect.Value          // receiver of methods for the service
   160		typ    reflect.Type           // type of the receiver
   161		method map[string]*methodType // registered methods
   162	}
   163	
   164	// Request is a header written before every RPC call.  It is used internally
   165	// but documented here as an aid to debugging, such as when analyzing
   166	// network traffic.
   167	type Request struct {
   168		ServiceMethod string   // format: "Service.Method"
   169		Seq           uint64   // sequence number chosen by client
   170		next          *Request // for free list in Server
   171	}
   172	
   173	// Response is a header written before every RPC return.  It is used internally
   174	// but documented here as an aid to debugging, such as when analyzing
   175	// network traffic.
   176	type Response struct {
   177		ServiceMethod string    // echoes that of the Request
   178		Seq           uint64    // echoes that of the request
   179		Error         string    // error, if any.
   180		next          *Response // for free list in Server
   181	}
   182	
   183	// Server represents an RPC Server.
   184	type Server struct {
   185		mu         sync.RWMutex // protects the serviceMap
   186		serviceMap map[string]*service
   187		reqLock    sync.Mutex // protects freeReq
   188		freeReq    *Request
   189		respLock   sync.Mutex // protects freeResp
   190		freeResp   *Response
   191	}
   192	
   193	// NewServer returns a new Server.
   194	func NewServer() *Server {
   195		return &Server{serviceMap: make(map[string]*service)}
   196	}
   197	
   198	// DefaultServer is the default instance of *Server.
   199	var DefaultServer = NewServer()
   200	
   201	// Is this an exported - upper case - name?
   202	func isExported(name string) bool {
   203		rune, _ := utf8.DecodeRuneInString(name)
   204		return unicode.IsUpper(rune)
   205	}
   206	
   207	// Is this type exported or a builtin?
   208	func isExportedOrBuiltinType(t reflect.Type) bool {
   209		for t.Kind() == reflect.Ptr {
   210			t = t.Elem()
   211		}
   212		// PkgPath will be non-empty even for an exported type,
   213		// so we need to check the type name as well.
   214		return isExported(t.Name()) || t.PkgPath() == ""
   215	}
   216	
   217	// Register publishes in the server the set of methods of the
   218	// receiver value that satisfy the following conditions:
   219	//	- exported method
   220	//	- two arguments, both pointers to exported structs
   221	//	- one return value, of type error
   222	// It returns an error if the receiver is not an exported type or has
   223	// no methods or unsuitable methods. It also logs the error using package log.
   224	// The client accesses each method using a string of the form "Type.Method",
   225	// where Type is the receiver's concrete type.
   226	func (server *Server) Register(rcvr interface{}) error {
   227		return server.register(rcvr, "", false)
   228	}
   229	
   230	// RegisterName is like Register but uses the provided name for the type
   231	// instead of the receiver's concrete type.
   232	func (server *Server) RegisterName(name string, rcvr interface{}) error {
   233		return server.register(rcvr, name, true)
   234	}
   235	
   236	func (server *Server) register(rcvr interface{}, name string, useName bool) error {
   237		server.mu.Lock()
   238		defer server.mu.Unlock()
   239		if server.serviceMap == nil {
   240			server.serviceMap = make(map[string]*service)
   241		}
   242		s := new(service)
   243		s.typ = reflect.TypeOf(rcvr)
   244		s.rcvr = reflect.ValueOf(rcvr)
   245		sname := reflect.Indirect(s.rcvr).Type().Name()
   246		if useName {
   247			sname = name
   248		}
   249		if sname == "" {
   250			s := "rpc.Register: no service name for type " + s.typ.String()
   251			log.Print(s)
   252			return errors.New(s)
   253		}
   254		if !isExported(sname) && !useName {
   255			s := "rpc.Register: type " + sname + " is not exported"
   256			log.Print(s)
   257			return errors.New(s)
   258		}
   259		if _, present := server.serviceMap[sname]; present {
   260			return errors.New("rpc: service already defined: " + sname)
   261		}
   262		s.name = sname
   263	
   264		// Install the methods
   265		s.method = suitableMethods(s.typ, true)
   266	
   267		if len(s.method) == 0 {
   268			str := ""
   269	
   270			// To help the user, see if a pointer receiver would work.
   271			method := suitableMethods(reflect.PtrTo(s.typ), false)
   272			if len(method) != 0 {
   273				str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
   274			} else {
   275				str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
   276			}
   277			log.Print(str)
   278			return errors.New(str)
   279		}
   280		server.serviceMap[s.name] = s
   281		return nil
   282	}
   283	
   284	// suitableMethods returns suitable Rpc methods of typ, it will report
   285	// error using log if reportErr is true.
   286	func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
   287		methods := make(map[string]*methodType)
   288		for m := 0; m < typ.NumMethod(); m++ {
   289			method := typ.Method(m)
   290			mtype := method.Type
   291			mname := method.Name
   292			// Method must be exported.
   293			if method.PkgPath != "" {
   294				continue
   295			}
   296			// Method needs three ins: receiver, *args, *reply.
   297			if mtype.NumIn() != 3 {
   298				if reportErr {
   299					log.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
   300				}
   301				continue
   302			}
   303			// First arg need not be a pointer.
   304			argType := mtype.In(1)
   305			if !isExportedOrBuiltinType(argType) {
   306				if reportErr {
   307					log.Println(mname, "argument type not exported:", argType)
   308				}
   309				continue
   310			}
   311			// Second arg must be a pointer.
   312			replyType := mtype.In(2)
   313			if replyType.Kind() != reflect.Ptr {
   314				if reportErr {
   315					log.Println("method", mname, "reply type not a pointer:", replyType)
   316				}
   317				continue
   318			}
   319			// Reply type must be exported.
   320			if !isExportedOrBuiltinType(replyType) {
   321				if reportErr {
   322					log.Println("method", mname, "reply type not exported:", replyType)
   323				}
   324				continue
   325			}
   326			// Method needs one out.
   327			if mtype.NumOut() != 1 {
   328				if reportErr {
   329					log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
   330				}
   331				continue
   332			}
   333			// The return type of the method must be error.
   334			if returnType := mtype.Out(0); returnType != typeOfError {
   335				if reportErr {
   336					log.Println("method", mname, "returns", returnType.String(), "not error")
   337				}
   338				continue
   339			}
   340			methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
   341		}
   342		return methods
   343	}
   344	
   345	// A value sent as a placeholder for the server's response value when the server
   346	// receives an invalid request. It is never decoded by the client since the Response
   347	// contains an error when it is used.
   348	var invalidRequest = struct{}{}
   349	
   350	func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) {
   351		resp := server.getResponse()
   352		// Encode the response header
   353		resp.ServiceMethod = req.ServiceMethod
   354		if errmsg != "" {
   355			resp.Error = errmsg
   356			reply = invalidRequest
   357		}
   358		resp.Seq = req.Seq
   359		sending.Lock()
   360		err := codec.WriteResponse(resp, reply)
   361		if debugLog && err != nil {
   362			log.Println("rpc: writing response:", err)
   363		}
   364		sending.Unlock()
   365		server.freeResponse(resp)
   366	}
   367	
   368	func (m *methodType) NumCalls() (n uint) {
   369		m.Lock()
   370		n = m.numCalls
   371		m.Unlock()
   372		return n
   373	}
   374	
   375	func (s *service) call(server *Server, sending *sync.Mutex, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) {
   376		mtype.Lock()
   377		mtype.numCalls++
   378		mtype.Unlock()
   379		function := mtype.method.Func
   380		// Invoke the method, providing a new value for the reply.
   381		returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
   382		// The return value for the method is an error.
   383		errInter := returnValues[0].Interface()
   384		errmsg := ""
   385		if errInter != nil {
   386			errmsg = errInter.(error).Error()
   387		}
   388		server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
   389		server.freeRequest(req)
   390	}
   391	
   392	type gobServerCodec struct {
   393		rwc    io.ReadWriteCloser
   394		dec    *gob.Decoder
   395		enc    *gob.Encoder
   396		encBuf *bufio.Writer
   397	}
   398	
   399	func (c *gobServerCodec) ReadRequestHeader(r *Request) error {
   400		return c.dec.Decode(r)
   401	}
   402	
   403	func (c *gobServerCodec) ReadRequestBody(body interface{}) error {
   404		return c.dec.Decode(body)
   405	}
   406	
   407	func (c *gobServerCodec) WriteResponse(r *Response, body interface{}) (err error) {
   408		if err = c.enc.Encode(r); err != nil {
   409			return
   410		}
   411		if err = c.enc.Encode(body); err != nil {
   412			return
   413		}
   414		return c.encBuf.Flush()
   415	}
   416	
   417	func (c *gobServerCodec) Close() error {
   418		return c.rwc.Close()
   419	}
   420	
   421	// ServeConn runs the server on a single connection.
   422	// ServeConn blocks, serving the connection until the client hangs up.
   423	// The caller typically invokes ServeConn in a go statement.
   424	// ServeConn uses the gob wire format (see package gob) on the
   425	// connection.  To use an alternate codec, use ServeCodec.
   426	func (server *Server) ServeConn(conn io.ReadWriteCloser) {
   427		buf := bufio.NewWriter(conn)
   428		srv := &gobServerCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(buf), buf}
   429		server.ServeCodec(srv)
   430	}
   431	
   432	// ServeCodec is like ServeConn but uses the specified codec to
   433	// decode requests and encode responses.
   434	func (server *Server) ServeCodec(codec ServerCodec) {
   435		sending := new(sync.Mutex)
   436		for {
   437			service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)
   438			if err != nil {
   439				if debugLog && err != io.EOF {
   440					log.Println("rpc:", err)
   441				}
   442				if !keepReading {
   443					break
   444				}
   445				// send a response if we actually managed to read a header.
   446				if req != nil {
   447					server.sendResponse(sending, req, invalidRequest, codec, err.Error())
   448					server.freeRequest(req)
   449				}
   450				continue
   451			}
   452			go service.call(server, sending, mtype, req, argv, replyv, codec)
   453		}
   454		codec.Close()
   455	}
   456	
   457	// ServeRequest is like ServeCodec but synchronously serves a single request.
   458	// It does not close the codec upon completion.
   459	func (server *Server) ServeRequest(codec ServerCodec) error {
   460		sending := new(sync.Mutex)
   461		service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)
   462		if err != nil {
   463			if !keepReading {
   464				return err
   465			}
   466			// send a response if we actually managed to read a header.
   467			if req != nil {
   468				server.sendResponse(sending, req, invalidRequest, codec, err.Error())
   469				server.freeRequest(req)
   470			}
   471			return err
   472		}
   473		service.call(server, sending, mtype, req, argv, replyv, codec)
   474		return nil
   475	}
   476	
   477	func (server *Server) getRequest() *Request {
   478		server.reqLock.Lock()
   479		req := server.freeReq
   480		if req == nil {
   481			req = new(Request)
   482		} else {
   483			server.freeReq = req.next
   484			*req = Request{}
   485		}
   486		server.reqLock.Unlock()
   487		return req
   488	}
   489	
   490	func (server *Server) freeRequest(req *Request) {
   491		server.reqLock.Lock()
   492		req.next = server.freeReq
   493		server.freeReq = req
   494		server.reqLock.Unlock()
   495	}
   496	
   497	func (server *Server) getResponse() *Response {
   498		server.respLock.Lock()
   499		resp := server.freeResp
   500		if resp == nil {
   501			resp = new(Response)
   502		} else {
   503			server.freeResp = resp.next
   504			*resp = Response{}
   505		}
   506		server.respLock.Unlock()
   507		return resp
   508	}
   509	
   510	func (server *Server) freeResponse(resp *Response) {
   511		server.respLock.Lock()
   512		resp.next = server.freeResp
   513		server.freeResp = resp
   514		server.respLock.Unlock()
   515	}
   516	
   517	func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, err error) {
   518		service, mtype, req, keepReading, err = server.readRequestHeader(codec)
   519		if err != nil {
   520			if !keepReading {
   521				return
   522			}
   523			// discard body
   524			codec.ReadRequestBody(nil)
   525			return
   526		}
   527	
   528		// Decode the argument value.
   529		argIsValue := false // if true, need to indirect before calling.
   530		if mtype.ArgType.Kind() == reflect.Ptr {
   531			argv = reflect.New(mtype.ArgType.Elem())
   532		} else {
   533			argv = reflect.New(mtype.ArgType)
   534			argIsValue = true
   535		}
   536		// argv guaranteed to be a pointer now.
   537		if err = codec.ReadRequestBody(argv.Interface()); err != nil {
   538			return
   539		}
   540		if argIsValue {
   541			argv = argv.Elem()
   542		}
   543	
   544		replyv = reflect.New(mtype.ReplyType.Elem())
   545		return
   546	}
   547	
   548	func (server *Server) readRequestHeader(codec ServerCodec) (service *service, mtype *methodType, req *Request, keepReading bool, err error) {
   549		// Grab the request header.
   550		req = server.getRequest()
   551		err = codec.ReadRequestHeader(req)
   552		if err != nil {
   553			req = nil
   554			if err == io.EOF || err == io.ErrUnexpectedEOF {
   555				return
   556			}
   557			err = errors.New("rpc: server cannot decode request: " + err.Error())
   558			return
   559		}
   560	
   561		// We read the header successfully.  If we see an error now,
   562		// we can still recover and move on to the next request.
   563		keepReading = true
   564	
   565		dot := strings.LastIndex(req.ServiceMethod, ".")
   566		if dot < 0 {
   567			err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
   568			return
   569		}
   570		serviceName := req.ServiceMethod[:dot]
   571		methodName := req.ServiceMethod[dot+1:]
   572	
   573		// Look up the request.
   574		server.mu.RLock()
   575		service = server.serviceMap[serviceName]
   576		server.mu.RUnlock()
   577		if service == nil {
   578			err = errors.New("rpc: can't find service " + req.ServiceMethod)
   579			return
   580		}
   581		mtype = service.method[methodName]
   582		if mtype == nil {
   583			err = errors.New("rpc: can't find method " + req.ServiceMethod)
   584		}
   585		return
   586	}
   587	
   588	// Accept accepts connections on the listener and serves requests
   589	// for each incoming connection.  Accept blocks; the caller typically
   590	// invokes it in a go statement.
   591	func (server *Server) Accept(lis net.Listener) {
   592		for {
   593			conn, err := lis.Accept()
   594			if err != nil {
   595				log.Fatal("rpc.Serve: accept:", err.Error()) // TODO(r): exit?
   596			}
   597			go server.ServeConn(conn)
   598		}
   599	}
   600	
   601	// Register publishes the receiver's methods in the DefaultServer.
   602	func Register(rcvr interface{}) error { return DefaultServer.Register(rcvr) }
   603	
   604	// RegisterName is like Register but uses the provided name for the type
   605	// instead of the receiver's concrete type.
   606	func RegisterName(name string, rcvr interface{}) error {
   607		return DefaultServer.RegisterName(name, rcvr)
   608	}
   609	
   610	// A ServerCodec implements reading of RPC requests and writing of
   611	// RPC responses for the server side of an RPC session.
   612	// The server calls ReadRequestHeader and ReadRequestBody in pairs
   613	// to read requests from the connection, and it calls WriteResponse to
   614	// write a response back.  The server calls Close when finished with the
   615	// connection. ReadRequestBody may be called with a nil
   616	// argument to force the body of the request to be read and discarded.
   617	type ServerCodec interface {
   618		ReadRequestHeader(*Request) error
   619		ReadRequestBody(interface{}) error
   620		// WriteResponse must be safe for concurrent use by multiple goroutines.
   621		WriteResponse(*Response, interface{}) error
   622	
   623		Close() error
   624	}
   625	
   626	// ServeConn runs the DefaultServer on a single connection.
   627	// ServeConn blocks, serving the connection until the client hangs up.
   628	// The caller typically invokes ServeConn in a go statement.
   629	// ServeConn uses the gob wire format (see package gob) on the
   630	// connection.  To use an alternate codec, use ServeCodec.
   631	func ServeConn(conn io.ReadWriteCloser) {
   632		DefaultServer.ServeConn(conn)
   633	}
   634	
   635	// ServeCodec is like ServeConn but uses the specified codec to
   636	// decode requests and encode responses.
   637	func ServeCodec(codec ServerCodec) {
   638		DefaultServer.ServeCodec(codec)
   639	}
   640	
   641	// ServeRequest is like ServeCodec but synchronously serves a single request.
   642	// It does not close the codec upon completion.
   643	func ServeRequest(codec ServerCodec) error {
   644		return DefaultServer.ServeRequest(codec)
   645	}
   646	
   647	// Accept accepts connections on the listener and serves requests
   648	// to DefaultServer for each incoming connection.
   649	// Accept blocks; the caller typically invokes it in a go statement.
   650	func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
   651	
   652	// Can connect to RPC service using HTTP CONNECT to rpcPath.
   653	var connected = "200 Connected to Go RPC"
   654	
   655	// ServeHTTP implements an http.Handler that answers RPC requests.
   656	func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   657		if req.Method != "CONNECT" {
   658			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   659			w.WriteHeader(http.StatusMethodNotAllowed)
   660			io.WriteString(w, "405 must CONNECT\n")
   661			return
   662		}
   663		conn, _, err := w.(http.Hijacker).Hijack()
   664		if err != nil {
   665			log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
   666			return
   667		}
   668		io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
   669		server.ServeConn(conn)
   670	}
   671	
   672	// HandleHTTP registers an HTTP handler for RPC messages on rpcPath,
   673	// and a debugging handler on debugPath.
   674	// It is still necessary to invoke http.Serve(), typically in a go statement.
   675	func (server *Server) HandleHTTP(rpcPath, debugPath string) {
   676		http.Handle(rpcPath, server)
   677		http.Handle(debugPath, debugHTTP{server})
   678	}
   679	
   680	// HandleHTTP registers an HTTP handler for RPC messages to DefaultServer
   681	// on DefaultRPCPath and a debugging handler on DefaultDebugPath.
   682	// It is still necessary to invoke http.Serve(), typically in a go statement.
   683	func HandleHTTP() {
   684		DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
   685	}

View as plain text