| Index: server/prpc/server.go
 | 
| diff --git a/server/prpc/server.go b/server/prpc/server.go
 | 
| index df43971e712a79a6b1a1ac4ed34302ea92bf26fd..1079cb108b4e78571084632669914db70b8b76e3 100644
 | 
| --- a/server/prpc/server.go
 | 
| +++ b/server/prpc/server.go
 | 
| @@ -5,6 +5,7 @@
 | 
|  package prpc
 | 
|  
 | 
|  import (
 | 
| +	"fmt"
 | 
|  	"net/http"
 | 
|  	"sort"
 | 
|  	"sync"
 | 
| @@ -12,7 +13,9 @@ import (
 | 
|  	"github.com/julienschmidt/httprouter"
 | 
|  	"golang.org/x/net/context"
 | 
|  	"google.golang.org/grpc"
 | 
| +	"google.golang.org/grpc/codes"
 | 
|  
 | 
| +	"github.com/luci/luci-go/common/logging"
 | 
|  	"github.com/luci/luci-go/server/auth"
 | 
|  	"github.com/luci/luci-go/server/middleware"
 | 
|  )
 | 
| @@ -55,7 +58,7 @@ func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
 | 
|  	if s.services == nil {
 | 
|  		s.services = map[string]*service{}
 | 
|  	} else if _, ok := s.services[desc.ServiceName]; ok {
 | 
| -		panicf("service %q is already registered", desc.ServiceName)
 | 
| +		panic(fmt.Errorf("service %q is already registered", desc.ServiceName))
 | 
|  	}
 | 
|  
 | 
|  	s.services[desc.ServiceName] = serv
 | 
| @@ -65,7 +68,7 @@ func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
 | 
|  func (s *Server) authenticate(base middleware.Base) middleware.Base {
 | 
|  	a := GetDefaultAuth()
 | 
|  	if a == nil {
 | 
| -		panicf("prpc: CustomAuthenticator is false, but default authenticator was not registered. " +
 | 
| +		panic("prpc: CustomAuthenticator is false, but default authenticator was not registered. " +
 | 
|  			"Forgot to import appengine/gaeauth/server package?")
 | 
|  	}
 | 
|  
 | 
| @@ -74,7 +77,8 @@ func (s *Server) authenticate(base middleware.Base) middleware.Base {
 | 
|  			c = auth.SetAuthenticator(c, a)
 | 
|  			c, err := a.Authenticate(c, r)
 | 
|  			if err != nil {
 | 
| -				writeError(c, w, withStatus(err, http.StatusUnauthorized))
 | 
| +				res := errResponse(codes.Unauthenticated, http.StatusUnauthorized, err.Error())
 | 
| +				res.write(c, w)
 | 
|  				return
 | 
|  			}
 | 
|  			h(c, w, r, p)
 | 
| @@ -82,8 +86,9 @@ func (s *Server) authenticate(base middleware.Base) middleware.Base {
 | 
|  	}
 | 
|  }
 | 
|  
 | 
| -// InstallHandlers installs HTTP POST handlers at
 | 
| -// /prpc/{service_name}/{method_name} for all registered services.
 | 
| +// InstallHandlers installs HTTP handlers at /prpc/:service/:method.
 | 
| +// See https://godoc.org/github.com/luci/luci-go/common/prpc#hdr-Protocol
 | 
| +// for pRPC protocol.
 | 
|  func (s *Server) InstallHandlers(r *httprouter.Router, base middleware.Base) {
 | 
|  	s.mu.Lock()
 | 
|  	defer s.mu.Unlock()
 | 
| @@ -92,11 +97,42 @@ func (s *Server) InstallHandlers(r *httprouter.Router, base middleware.Base) {
 | 
|  		base = s.authenticate(base)
 | 
|  	}
 | 
|  
 | 
| -	for _, service := range s.services {
 | 
| -		for _, m := range service.methods {
 | 
| -			m.InstallHandlers(r, base)
 | 
| -		}
 | 
| +	r.POST("/prpc/:service/:method", base(s.handle))
 | 
| +}
 | 
| +
 | 
| +// handle handles RPCs.
 | 
| +// See https://godoc.org/github.com/luci/luci-go/common/prpc#hdr-Protocol
 | 
| +// for pRPC protocol.
 | 
| +func (s *Server) handle(c context.Context, w http.ResponseWriter, r *http.Request, p httprouter.Params) {
 | 
| +	serviceName := p.ByName("service")
 | 
| +	methodName := p.ByName("method")
 | 
| +	res := s.respond(c, w, r, serviceName, methodName)
 | 
| +
 | 
| +	c = logging.SetFields(c, logging.Fields{
 | 
| +		"service": serviceName,
 | 
| +		"method":  methodName,
 | 
| +	})
 | 
| +	res.write(c, w)
 | 
| +}
 | 
| +
 | 
| +func (s *Server) respond(c context.Context, w http.ResponseWriter, r *http.Request, serviceName, methodName string) *response {
 | 
| +	service := s.services[serviceName]
 | 
| +	if service == nil {
 | 
| +		return errResponse(
 | 
| +			codes.Unimplemented,
 | 
| +			http.StatusNotImplemented,
 | 
| +			fmt.Sprintf("service %q is not implemented", serviceName))
 | 
| +	}
 | 
| +
 | 
| +	method := service.methods[methodName]
 | 
| +	if method == nil {
 | 
| +		return errResponse(
 | 
| +			codes.Unimplemented,
 | 
| +			http.StatusNotImplemented,
 | 
| +			fmt.Sprintf("method %q in service %q is not implemented", methodName, serviceName))
 | 
|  	}
 | 
| +
 | 
| +	return method.handle(c, w, r)
 | 
|  }
 | 
|  
 | 
|  // ServiceNames returns a sorted list of full names of all registered services.
 | 
| 
 |