Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package main | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "fmt" | |
| 10 "io" | |
| 11 "net/http" | |
| 12 "net/url" | |
| 13 "os" | |
| 14 "sort" | |
| 15 "strings" | |
| 16 | |
| 17 "github.com/maruel/subcommands" | |
| 18 "golang.org/x/net/context" | |
| 19 | |
| 20 "github.com/luci/luci-go/common/logging" | |
| 21 ) | |
| 22 | |
| 23 type format string | |
| 24 | |
| 25 const ( | |
| 26 formatBinary format = "binary" | |
| 27 formatJSON format = "json" | |
| 28 formatText format = "text" | |
| 29 ) | |
| 30 | |
| 31 // TODO(nodir): support specifying message fields with options, e.g. | |
| 32 // rpc call :8080 helloworld.Greeter -name Lucy | |
| 33 // It will be the default request format. | |
| 34 | |
| 35 var cmdCall = &subcommands.Command{ | |
| 36 UsageLine: `call [flags] <server> <service>.<method> | |
| 37 | |
| 38 server: scheme (http or https) and hostname, e.g. "https://example.com". | |
|
iannucci
2016/01/15 03:45:16
can we ONLY support https?
...
Oh, dev_appserver
nodir
2016/01/15 18:17:19
Done. Also simplified usage string and parsing fun
| |
| 39 A short syntax for "http://localhost:port" is ":port", e.g. ":8080". | |
| 40 service: full name of a service, e.g. "pkg.service" | |
| 41 method: name of the method. | |
| 42 | |
| 43 Flags:`, | |
| 44 ShortDesc: "calls a service method.", | |
| 45 LongDesc: "Calls a service method.", | |
| 46 CommandRun: func() subcommands.CommandRun { | |
| 47 c := &callRun{} | |
| 48 c.registerBaseFlags() | |
| 49 c.Flags.StringVar(&c.format, "format", string(formatJSON), fmt.S printf( | |
| 50 "Request format, read from stdin. Valid values: %q, %q, %q.", | |
| 51 formatJSON, formatBinary, formatText, | |
| 52 )) | |
| 53 return c | |
| 54 }, | |
| 55 } | |
| 56 | |
| 57 // callRun implements "call" subcommand. | |
| 58 type callRun struct { | |
| 59 cmdRun | |
| 60 format string | |
| 61 message string | |
| 62 } | |
| 63 | |
| 64 func (r *callRun) Run(a subcommands.Application, args []string) int { | |
| 65 if len(args) != 2 { | |
| 66 return r.argErr("") | |
| 67 } | |
| 68 var req request | |
| 69 var err error | |
| 70 req.server, err = parseServer(args[0]) | |
| 71 if err != nil { | |
| 72 return r.argErr("server: %s", err) | |
| 73 } | |
| 74 req.service, req.method, err = splitServiceAndMethod(args[1]) | |
| 75 if err != nil { | |
| 76 return r.argErr("%s", err) | |
| 77 } | |
| 78 | |
| 79 switch f := format(r.format); f { | |
| 80 case formatJSON, formatBinary, formatText: | |
| 81 req.format = f | |
| 82 default: | |
| 83 return r.argErr("invalid format %q", f) | |
| 84 } | |
| 85 | |
| 86 var msg bytes.Buffer | |
| 87 if _, err := io.Copy(&msg, os.Stdin); err != nil { | |
| 88 return r.done(fmt.Errorf("could not read message from stdin: %s" , err)) | |
| 89 } | |
| 90 req.message = msg.Bytes() | |
| 91 return r.done(call(r.initContext(), &req, os.Stdout)) | |
| 92 } | |
| 93 | |
| 94 func splitServiceAndMethod(fullName string) (service string, method string, err error) { | |
| 95 lastDot := strings.LastIndex(fullName, ".") | |
| 96 if lastDot < 0 { | |
| 97 return "", "", fmt.Errorf("invalid full method name %q. It must contain a '.'", fullName) | |
| 98 } | |
| 99 service = fullName[:lastDot] | |
| 100 method = fullName[lastDot+1:] | |
| 101 return | |
| 102 } | |
| 103 | |
| 104 // request is an RPC request. | |
| 105 type request struct { | |
| 106 server *url.URL | |
| 107 service string | |
| 108 method string | |
| 109 format format | |
| 110 message []byte | |
| 111 } | |
| 112 | |
| 113 // call makes an RPC and writes response to out. | |
| 114 func call(c context.Context, req *request, out io.Writer) error { | |
| 115 if req.format == "" { | |
| 116 return fmt.Errorf("format is not set") | |
| 117 } | |
| 118 | |
| 119 // Create HTTP request. | |
| 120 methodURL := *req.server | |
| 121 methodURL.Path = fmt.Sprintf("/prpc/%s/%s", req.service, req.method) | |
| 122 hr, err := http.NewRequest("POST", methodURL.String(), bytes.NewBuffer(r eq.message)) | |
| 123 if err != nil { | |
| 124 return err | |
| 125 } | |
| 126 | |
| 127 // Set headers. | |
| 128 mediaType := "application/prpc; encoding=" + string(req.format) | |
| 129 if len(req.message) > 0 { | |
| 130 hr.Header.Set("Content-Type", mediaType) | |
| 131 } | |
| 132 hr.Header.Set("Accept", mediaType) | |
| 133 hr.Header.Set("User-Agent", userAgent) | |
| 134 hr.Header.Set("Content-Length", fmt.Sprintf("%d", len(req.message))) | |
| 135 // TODO(nodir): add "Accept-Encoding: gzip" when pRPC server supports it . | |
| 136 | |
| 137 // Log request in curl style. | |
| 138 logRequest(c, hr) | |
| 139 logging.Infof(c, ">") | |
| 140 | |
| 141 // Send the request. | |
| 142 var client http.Client | |
| 143 res, err := client.Do(hr) | |
| 144 if err != nil { | |
| 145 return fmt.Errorf("failed to send request: %s", err) | |
| 146 } | |
| 147 defer res.Body.Close() | |
| 148 | |
| 149 // Log response in curl style. | |
| 150 logResponse(c, res) | |
| 151 logging.Infof(c, "<") | |
|
iannucci
2016/01/15 03:45:16
do we log to stderr so that you can pipe the outpu
nodir
2016/01/15 18:17:19
yes to stderr, see https://chromiumcodereview.apps
| |
| 152 | |
| 153 // Read response. | |
| 154 if _, err := io.Copy(out, res.Body); err != nil { | |
| 155 return fmt.Errorf("failed to read response: %s", err) | |
| 156 } | |
| 157 return err | |
| 158 } | |
| 159 | |
| 160 // logRequest logs an HTTP request in curl style. | |
| 161 func logRequest(c context.Context, r *http.Request) { | |
| 162 logging.Infof(c, "> %s %s %s", r.Method, r.URL.Path, r.Proto) | |
| 163 logging.Infof(c, "> Host: %s", r.URL.Host) | |
| 164 logHeaders(c, r.Header, "> ") | |
| 165 } | |
| 166 | |
| 167 // logResponse logs an HTTP response in curl style. | |
| 168 func logResponse(c context.Context, r *http.Response) { | |
| 169 logging.Infof(c, "< %s %s", r.Proto, r.Status) | |
| 170 logHeaders(c, r.Header, "< ") | |
| 171 | |
| 172 } | |
| 173 | |
| 174 // logHeaders logs all headers sorted. | |
| 175 func logHeaders(c context.Context, header http.Header, prefix string) { | |
| 176 var names []string | |
| 177 for n := range header { | |
| 178 names = append(names, n) | |
| 179 } | |
| 180 sort.Strings(names) | |
| 181 for _, name := range names { | |
| 182 for _, v := range header[name] { | |
| 183 logging.Infof(c, "%s%s: %s", prefix, name, v) | |
| 184 } | |
| 185 } | |
| 186 } | |
| OLD | NEW |