// getConfig retrieves the configuration for the specified key. If the // key is empty, all configurations are returned. Otherwise, the // leading "/" path delimiter is stripped and the configuration // matching the remainder is retrieved. Note that this will retrieve // the default config if "key" is equal to "/", and will list all // configs if "key" is equal to "". The body result contains a listing // of keys and retrieval of a config. The output format is determined // by the request header. func getConfig(db *client.DB, configPrefix proto.Key, config gogoproto.Message, path string, r *http.Request) (body []byte, contentType string, err error) { // Scan all configs if the key is empty. if len(path) == 0 { var rows []client.KeyValue if rows, err = db.Scan(configPrefix, configPrefix.PrefixEnd(), maxGetResults); err != nil { return } if len(rows) == maxGetResults { log.Warningf("retrieved maximum number of results (%d); some may be missing", maxGetResults) } var prefixes []string for _, row := range rows { trimmed := bytes.TrimPrefix(row.Key, configPrefix) prefixes = append(prefixes, url.QueryEscape(string(trimmed))) } // Encode the response. body, contentType, err = util.MarshalResponse(r, prefixes, util.AllEncodings) } else { configkey := keys.MakeKey(configPrefix, proto.Key(path[1:])) if err = db.GetProto(configkey, config); err != nil { return } body, contentType, err = util.MarshalResponse(r, config, util.AllEncodings) } return }
// handleStoreStatus handles GET requests for a single node's status. If no id // is available, it calls handleStoresStatus to return all store's statuses. func (s *statusServer) handleStoreStatus(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { id, err := strconv.ParseInt(ps.ByName("id"), 10, 32) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } key := keys.StoreStatusKey(int32(id)) storeStatus := &storage.StoreStatus{} if err := s.db.GetProto(key, storeStatus); err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } b, contentType, err := util.MarshalResponse(r, storeStatus, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }
// handleLocalLogFile handles GET requests for a single log. If no filename is // available, it returns 404. The log contents are returned in structured // format as JSON. func (s *statusServer) handleLocalLogFile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { log.Flush() file := ps.ByName("file") reader, err := log.GetLogReader(file, false /* !allowAbsolute */) if reader == nil || err != nil { log.Errorf("unable to open log file %s: %s", file, err) http.NotFound(w, r) return } defer reader.Close() entry := log.LogEntry{} var entries []log.LogEntry decoder := log.NewEntryDecoder(reader) for { if err := decoder.Decode(&entry); err != nil { if err == io.EOF { break } log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } entries = append(entries, entry) } b, contentType, err := util.MarshalResponse(r, entries, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }
// Verify that marshal response protects against returning // unguarded slice or array types. func TestMarshalResponseSlice(t *testing.T) { // We expect the array to be wrapped in a struct with data key "d". expBody := []byte(`{ "d": [ 1, 2, 3 ] }`) // Respond with JSON versions of a slice and an array of integers from 1,2,3. for i, value := range []interface{}{[]int{1, 2, 3}, [3]int{1, 2, 3}} { req, err := http.NewRequest("GET", "http://foo.com", nil) if err != nil { t.Fatal(err) } req.Header.Add(util.ContentTypeHeader, util.JSONContentType) req.Header.Add(util.AcceptHeader, util.JSONContentType) body, _, err := util.MarshalResponse(req, value, util.AllEncodings) if err != nil { t.Fatalf("%d: %s", i, err) } if !bytes.Equal(body, expBody) { t.Errorf("%d: expected %q; got %q", i, expBody, body) } } }
// TestProtoEncodingError verifies that MarshalResponse and // UnmarshalRequest gracefully handles a protocol message type error. func TestProtoEncodingError(t *testing.T) { req, err := http.NewRequest("GET", "http://foo.com", nil) if err != nil { t.Fatal(err) } req.Header.Add(util.ContentTypeHeader, util.ProtoContentType) reqBody := []byte("foo") var value string err = util.UnmarshalRequest(req, reqBody, value, []util.EncodingType{util.ProtoEncoding}) if err == nil { t.Errorf("unexpected success") } req.Header.Add(util.AcceptHeader, util.ProtoContentType) body, cType, err := util.MarshalResponse(req, value, []util.EncodingType{util.ProtoEncoding}) if err != nil { t.Fatal(err) } if cType != util.JSONContentType { t.Errorf("unexpected content type; got %s", cType) } if !bytes.Equal(body, body) { t.Errorf("unexpected boy; got %s", body) } }
// handleStoresStatus handles GET requests for all store statuses. func (s *statusServer) handleStoresStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { startKey := keys.StatusStorePrefix endKey := startKey.PrefixEnd() rows, err := s.db.Scan(startKey, endKey, 0) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } storeStatuses := []storage.StoreStatus{} for _, row := range rows { storeStatus := &storage.StoreStatus{} if err := row.ValueProto(storeStatus); err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } storeStatuses = append(storeStatuses, *storeStatus) } b, contentType, err := util.MarshalResponse(r, storeStatuses, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }
// ServeHTTP serves the SQL API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according to the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() method := r.URL.Path if !strings.HasPrefix(method, driver.Endpoint) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Check TLS settings. authenticationHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } method = strings.TrimPrefix(method, driver.Endpoint) if method != driver.Execute.String() { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var args driver.Request if err := util.UnmarshalRequest(r, reqBody, &args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check request user against client certificate user. if err := authenticationHook(&args); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Send the Request for SQL execution and set the application-level error // on the reply. reply, err := s.exec(args) if err != nil { errProto := proto.Error{} errProto.SetResponseGoError(err) reply.Error = &errProto } // Marshal the response. body, contentType, err := util.MarshalResponse(r, &reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) }
// TestHTTPSenderSend verifies sending posts. func TestHTTPSenderSend(t *testing.T) { defer leaktest.AfterTest(t) server, addr := startTestHTTPServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Make sure SSL certs were properly specified. authenticationHook, err := security.AuthenticationHook(false /* !insecure */, r.TLS) if err != nil { t.Error(err) } if r.Method != "POST" { t.Errorf("expected method POST; got %s", r.Method) } if r.URL.Path != KVDBEndpoint+"Put" { t.Errorf("expected url %s; got %s", KVDBEndpoint+"Put", r.URL.Path) } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { t.Errorf("unexpected error reading body: %s", err) } args := &proto.PutRequest{} if err := util.UnmarshalRequest(r, reqBody, args, util.AllEncodings); err != nil { t.Errorf("unexpected error unmarshalling request: %s", err) } // Validate request against incoming user. if err := authenticationHook(args, false /*not public*/); err != nil { t.Error(err) } if !args.Key.Equal(testPutReq.Key) || !args.Timestamp.Equal(testPutReq.Timestamp) { t.Errorf("expected parsed %+v to equal %+v", args, testPutReq) } body, contentType, err := util.MarshalResponse(r, testPutResp, util.AllEncodings) if err != nil { t.Errorf("failed to marshal response: %s", err) } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) })) defer server.Close() sender, err := newHTTPSender(addr, testutils.NewNodeTestBaseContext(), defaultRetryOptions) if err != nil { t.Fatal(err) } reply := &proto.PutResponse{} sender.Send(context.Background(), proto.Call{Args: testPutReq, Reply: reply}) if reply.GoError() != nil { t.Errorf("expected success; got %s", reply.GoError()) } if !reply.Timestamp.Equal(testPutResp.Timestamp) { t.Errorf("expected received %+v to equal %+v", reply, testPutResp) } }
// ServeHTTP serves the SQL API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according to the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() method := r.URL.Path if !strings.HasPrefix(method, driver.Endpoint) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Check TLS settings. authenticationHook, err := security.ProtoAuthHook(s.context.Insecure, r.TLS) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } method = strings.TrimPrefix(method, driver.Endpoint) if method != driver.Execute.String() { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var args driver.Request if err := util.UnmarshalRequest(r, reqBody, &args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check request user against client certificate user. if err := authenticationHook(&args, true /* public */); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } reply, code, err := s.Execute(args) if err != nil { http.Error(w, err.Error(), code) } // Marshal the response. body, contentType, err := util.MarshalResponse(r, &reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) if _, err := w.Write(body); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
// ServeHTTP serves the key-value API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according the the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *DBServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Check TLS settings before anything else. authenticationHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } method := r.URL.Path if !strings.HasPrefix(method, DBPrefix) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } method = strings.TrimPrefix(method, DBPrefix) args, reply := createArgsAndReply(method) if args == nil { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := util.UnmarshalRequest(r, reqBody, args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Verify the request for public API. if err := verifyRequest(args); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check request user against client certificate user. if err := authenticationHook(args); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Create a call and invoke through sender. s.sender.Send(context.TODO(), proto.Call{Args: args, Reply: reply}) // Marshal the response. body, contentType, err := util.MarshalResponse(r, reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) }
// handleQuery handles an incoming HTTP query request. Each query requests data // for one or more metrics over a specific time span. Query requests have a // significant body and thus are POST requests. func (s *Server) handleQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { request := &TimeSeriesQueryRequest{} // Unmarshal query request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := util.UnmarshalRequest(r, reqBody, request, util.AllEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if len(request.Queries) == 0 { http.Error(w, "time series query requests must specify at least one query.", http.StatusBadRequest) return } response := &TimeSeriesQueryResponse{ Results: make([]*TimeSeriesQueryResponse_Result, 0, len(request.Queries)), } for _, q := range request.Queries { datapoints, sources, err := s.db.Query(q, Resolution10s, request.StartNanos, request.EndNanos) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } result := &TimeSeriesQueryResponse_Result{ Query: q, Datapoints: datapoints, } // TODO(tamird): Remove this (and all other) explicit setting of defaults. // It is currently required because the client side doesn't know about // proto defaults. result.SourceAggregator = q.GetSourceAggregator().Enum() result.Downsampler = q.GetDownsampler().Enum() result.Derivative = q.GetDerivative().Enum() result.Sources = sources response.Results = append(response.Results, result) } // Marshal and return response. b, contentType, err := util.MarshalResponse(r, response, util.AllEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) if _, err := w.Write(b); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
// Get retrieves the perm configuration for the specified key. If the // key is empty, all perm configurations are returned. Otherwise, the // leading "/" path delimiter is stripped and the perm configuration // matching the remainder is retrieved. Note that this will retrieve // the default perm config if "key" is equal to "/", and will list all // configs if "key" is equal to "". The body result contains // JSON-formatted output for a listing of keys and JSON-formatted // output for retrieval of a perm config. func (ph *permHandler) Get(path string, r *http.Request) (body []byte, contentType string, err error) { // Scan all perms if the key is empty. if len(path) == 0 { sr := &proto.ScanResponse{} if err = ph.db.Call(proto.Scan, &proto.ScanRequest{ RequestHeader: proto.RequestHeader{ Key: engine.KeyConfigPermissionPrefix, EndKey: engine.KeyConfigPermissionPrefix.PrefixEnd(), User: storage.UserRoot, }, MaxResults: maxGetResults, }, sr); err != nil { return } if len(sr.Rows) == maxGetResults { log.Warningf("retrieved maximum number of results (%d); some may be missing", maxGetResults) } var prefixes []string for _, kv := range sr.Rows { trimmed := bytes.TrimPrefix(kv.Key, engine.KeyConfigPermissionPrefix) prefixes = append(prefixes, url.QueryEscape(string(trimmed))) } // Encode the response. body, contentType, err = util.MarshalResponse(r, prefixes, util.AllEncodings) } else { permKey := engine.MakeKey(engine.KeyConfigPermissionPrefix, proto.Key(path[1:])) var ok bool config := &proto.PermConfig{} if ok, _, err = ph.db.GetProto(permKey, config); err != nil { return } // On get, if there's no perm config for the requested prefix, // return a not found error. if !ok { err = util.Errorf("no config found for key prefix %q", path) return } body, contentType, err = util.MarshalResponse(r, config, util.AllEncodings) } return }
// ServeHTTP serves the key-value API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according the the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *DBServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { method := r.URL.Path if !strings.HasPrefix(method, DBPrefix) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } method = strings.TrimPrefix(method, DBPrefix) if !proto.IsPublic(method) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } args, reply, err := proto.CreateArgsAndReply(method) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := util.UnmarshalRequest(r, reqBody, args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Verify the request for public API. if err := verifyRequest(args); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Create a call and invoke through sender. call := &client.Call{ Method: method, Args: args, Reply: reply, } s.sender.Send(call) // Marshal the response. body, contentType, err := util.MarshalResponse(r, reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", contentType) w.Write(body) }
// TestHTTPSenderRetryHTTPSendError verifies that send is retried // on all errors sending HTTP requests. func TestHTTPSenderRetryHTTPSendError(t *testing.T) { defer leaktest.AfterTest(t) retryOptions := defaultRetryOptions retryOptions.InitialBackoff = 1 * time.Millisecond testCases := []func(*httptest.Server, http.ResponseWriter){ // Send back an unparseable response but a success code on first try. func(s *httptest.Server, w http.ResponseWriter) { fmt.Fprintf(w, "\xff\xfe\x23\x44") }, // Close the client connection. func(s *httptest.Server, w http.ResponseWriter) { s.CloseClientConnections() }, } for i, testFunc := range testCases { count := 0 var s *httptest.Server server, addr := startTestHTTPServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { count++ if count == 1 { // On first retry, invoke the error function. testFunc(s, w) return } // Success on second try. body, contentType, err := util.MarshalResponse(r, testPutResp, util.AllEncodings) if err != nil { t.Errorf("%d: failed to marshal response: %s", i, err) } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) })) s = server sender, err := newHTTPSender(addr, testutils.NewRootTestBaseContext(), retryOptions) if err != nil { t.Fatal(err) } reply := &proto.PutResponse{} sender.Send(context.Background(), proto.Call{Args: testPutReq, Reply: reply}) if reply.GoError() != nil { t.Errorf("%d: expected success; got %s", i, reply.GoError()) } if count != 2 { t.Errorf("%d: expected retry", i) } server.Close() } }
// handleQuery handles an incoming HTTP query request. Each query requests data // for one or more metrics over a specific time span. Query requests have a // significant body and thus are POST requests. func (s *Server) handleQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { request := &TimeSeriesQueryRequest{} // Unmarshal query request. reqBody, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := util.UnmarshalRequest(r, reqBody, request, util.AllEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if len(request.Queries) == 0 { http.Error(w, "time series query requests must specify at least one query.", http.StatusBadRequest) return } response := &TimeSeriesQueryResponse{ Results: make([]*TimeSeriesQueryResponse_Result, 0, len(request.Queries)), } for _, q := range request.Queries { datapoints, sources, err := s.db.Query(q, Resolution10s, request.StartNanos, request.EndNanos) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } response.Results = append(response.Results, &TimeSeriesQueryResponse_Result{ Name: q.Name, Sources: sources, Datapoints: datapoints, Aggregator: q.Aggregator, }) } // Marshal and return response. b, contentType, err := util.MarshalResponse(r, response, util.AllEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) if _, err := w.Write(b); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
func TestMarshalResponse(t *testing.T) { testCases := []struct { cType, accept string expCType string expBody []byte }{ {"", "application/json", "application/json", jsonConfig}, {"", "application/x-json", "application/json", jsonConfig}, {"application/json", "", "application/json", jsonConfig}, {"application/json", "foo", "application/json", jsonConfig}, {"", "application/x-google-protobuf", "application/x-protobuf", protobufConfig}, {"", "application/x-protobuf", "application/x-protobuf", protobufConfig}, {"application/x-protobuf", "", "application/x-protobuf", protobufConfig}, {"application/x-protobuf", "foo", "application/x-protobuf", protobufConfig}, {"", "text/yaml", "text/yaml", yamlConfig}, {"", "application/x-yaml", "text/yaml", yamlConfig}, {"text/yaml", "", "text/yaml", yamlConfig}, {"text/yaml", "foo", "text/yaml", yamlConfig}, // Test mixed accept headers; but we ignore quality. {"", "application/json, application/x-protobuf; q=0.8", "application/json", jsonConfig}, {"", "application/json, application/x-protobuf; q=0.8, text/yaml; q=0.5", "application/json", jsonConfig}, {"", "application/x-protobuf; q=0.8, text/yaml; q=0.5, application/json", "application/x-protobuf", protobufConfig}, {"", "text/yaml; q=0.5, application/x-protobuf; q=0.8, application/json", "text/yaml", yamlConfig}, // Test default encoding is JSON. {"foo", "foo", "application/json", jsonConfig}, } for i, test := range testCases { req, err := http.NewRequest("GET", "http://foo.com", nil) if err != nil { t.Fatal(err) } req.Header.Add(util.ContentTypeHeader, test.cType) req.Header.Add(util.AcceptHeader, test.accept) body, cType, err := util.MarshalResponse(req, &testConfig, util.AllEncodings) if err != nil { t.Fatalf("%d: %s", i, err) } if !bytes.Equal(body, test.expBody) { t.Errorf("%d: expected %q; got %q", i, test.expBody, body) } if cType != test.expCType { t.Errorf("%d: expected %s content type; got %s", i, test.expCType, cType) } } }
// handleLocalLogFiles handles GET requests for list of available logs. func (s *statusServer) handleLocalLogFiles(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { log.Flush() logFiles, err := log.ListLogFiles() if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } b, contentType, err := util.MarshalResponse(r, logFiles, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }
// handleLocalStatus handles GET requests for local-node status. func (s *statusServer) handleLocalStatus(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { local := struct { Address util.UnresolvedAddr `json:"address"` BuildInfo util.BuildInfo `json:"buildInfo"` }{ BuildInfo: util.GetBuildInfo(), } if addr, err := s.gossip.GetNodeIDAddress(s.gossip.GetNodeID()); err == nil { local.Address = addr.(util.UnresolvedAddr) } b, contentType, err := util.MarshalResponse(r, local, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }
// ServeHTTP serves the SQL API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according to the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { method := r.URL.Path if !strings.HasPrefix(method, sqlwire.Endpoint) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } method = strings.TrimPrefix(method, sqlwire.Endpoint) args, reply := createArgsAndReply(method) if args == nil { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := util.UnmarshalRequest(r, reqBody, args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Send the SQLRequest for SQL execution. if httpStatus, err := s.execute(args, reply); err != nil { http.Error(w, err.Error(), httpStatus) return } // Marshal the response. body, contentType, err := util.MarshalResponse(r, reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) }
// ServeHTTP serves the SQL API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according to the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() method := r.URL.Path if !strings.HasPrefix(method, driver.Endpoint) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Check TLS settings. authenticationHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } method = strings.TrimPrefix(method, driver.Endpoint) if method != driver.Execute.String() { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var args driver.Request if err := util.UnmarshalRequest(r, reqBody, &args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check request user against client certificate user. if err := authenticationHook(&args, true /*public*/); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Pick up current session state. planMaker := planner{user: args.GetUser()} if err := gogoproto.Unmarshal(args.Session, &planMaker.session); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Open a pending transaction if needed. if planMaker.session.Txn != nil { txn := client.NewTxn(*s.db) txn.Proto = *planMaker.session.Txn planMaker.txn = txn } // Send the Request for SQL execution and set the application-level error // for each result in the reply. reply := s.exec(args, &planMaker) // Send back the session state even if there were application-level errors. // Add transaction to session state. if planMaker.txn != nil { planMaker.session.Txn = &planMaker.txn.Proto } else { planMaker.session.Txn = nil } bytes, err := gogoproto.Marshal(&planMaker.session) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } reply.Session = bytes // Marshal the response. body, contentType, err := util.MarshalResponse(r, &reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) }
// TestHTTPSenderRetryResponseCodes verifies that send is retried // on some HTTP response codes but not on others. func TestHTTPSenderRetryResponseCodes(t *testing.T) { defer leaktest.AfterTest(t) retryOptions := defaultRetryOptions retryOptions.InitialBackoff = 1 * time.Millisecond testCases := []struct { code int retry bool }{ {http.StatusServiceUnavailable, true}, {http.StatusGatewayTimeout, true}, {StatusTooManyRequests, true}, {http.StatusRequestTimeout, false}, {http.StatusBadRequest, false}, {http.StatusNotFound, false}, {http.StatusUnauthorized, false}, {http.StatusForbidden, false}, {http.StatusMethodNotAllowed, false}, {http.StatusNotAcceptable, false}, {http.StatusInternalServerError, false}, {http.StatusNotImplemented, false}, } for i, test := range testCases { count := 0 server, addr := startTestHTTPServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { count++ if count == 1 { http.Error(w, "manufactured error", test.code) return } if !test.retry { t.Errorf("%d: didn't expect retry on code %d", i, test.code) } body, contentType, err := util.MarshalResponse(r, testPutResp, util.AllEncodings) if err != nil { t.Errorf("%d: failed to marshal response: %s", i, err) } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) })) sender, err := newHTTPSender(addr, testutils.NewRootTestBaseContext(), retryOptions) if err != nil { t.Fatal(err) } reply := &proto.PutResponse{} sender.Send(context.Background(), proto.Call{Args: testPutReq, Reply: reply}) if test.retry { if count != 2 { t.Errorf("%d: expected retry", i) } if reply.GoError() != nil { t.Errorf("%d: expected success after retry; got %s", i, reply.GoError()) } } else { if count != 1 { t.Errorf("%d; expected no retry; got %d", i, count) } if reply.GoError() == nil { t.Errorf("%d: expected error", i) } } server.Close() } }
// handleLocalLog returns the log entries parsed from the log files stored on // the server. Log entries are returned in reverse chronological order. The // following options are available: // * "starttime" query parameter filters the log entries to only ones that // occurred on or after the "starttime". Defaults to a day ago. // * "endtime" query parameter filters the log entries to only ones that // occurred before on on the "endtime". Defaults to the current time. // * "pattern" query parameter filters the log entries by the provided regexp // pattern if it exists. Defaults to nil. // * "max" query parameter is the hard limit of the number of returned log // entries. Defaults to defaultMaxLogEntries. // * "level" which is an optional part of the URL filters the log entries to be // those of the corresponding severity level or worse. Defaults to "info". func (s *statusServer) handleLocalLog(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { log.Flush() level := ps.ByName("level") var sev log.Severity if len(level) == 0 { sev = log.InfoLog } else { var sevFound bool sev, sevFound = log.SeverityByName(level) if !sevFound { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "could not determine level %s", level) return } } startTimestamp, err := parseInt64WithDefault( r.URL.Query().Get("starttime"), time.Now().AddDate(0, 0, -1).UnixNano()) if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "starttime could not be parsed:%s", err) return } endTimestamp, err := parseInt64WithDefault( r.URL.Query().Get("endtime"), time.Now().UnixNano()) if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "endtime could not be parsed:%s", err) return } if startTimestamp > endTimestamp { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "startime:%d is greater than endtime:%d", startTimestamp, endTimestamp) return } maxEntries, err := parseInt64WithDefault( r.URL.Query().Get("max"), defaultMaxLogEntries) if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "max could not be parsed:%s", err) return } if maxEntries < 1 { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "max must be set to a value greater than 0") return } pattern := r.URL.Query().Get("pattern") var regex *regexp.Regexp if len(pattern) > 0 { if regex, err = regexp.Compile(pattern); err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "could not compile regex pattern:%s", err) return } } entries, err := log.FetchEntriesFromFiles(sev, startTimestamp, endTimestamp, int(maxEntries), regex) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } b, contentType, err := util.MarshalResponse(r, entries, []util.EncodingType{util.JSONEncoding}) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(b) }