@@ -3,12 +3,14 @@ package otlp
33import (
44 "context"
55 "fmt"
6+ "io"
67 "net/http"
78 "strings"
89
910 "google.golang.org/grpc"
1011 "google.golang.org/grpc/codes"
1112 "google.golang.org/grpc/keepalive"
13+ "google.golang.org/protobuf/encoding/protojson"
1214 "google.golang.org/protobuf/proto"
1315
1416 "github.com/go-kit/log"
@@ -61,10 +63,14 @@ func NewOTLPIngestHandler(cfg server.Config, svc PushService, l log.Logger, me b
6163 return
6264 }
6365
64- // Handle HTTP requests (if we want to support HTTP/Protobuf in future)
65- //if r.URL.Path == "/v1/profiles" {}
66+ // Handle HTTP/Protobuf and HTTP/Binary requests
67+ contentType := r .Header .Get ("Content-Type" )
68+ if contentType == "application/json" || contentType == "application/x-protobuf" || contentType == "application/protobuf" {
69+ h .handleHTTPProtobuf (w , r )
70+ return
71+ }
6672
67- http .Error (w , "Method not allowed " , http .StatusMethodNotAllowed )
73+ http .Error (w , fmt . Sprintf ( "Unsupported Content-Type: %s " , contentType ), http .StatusUnsupportedMediaType )
6874 })
6975
7076 return h
@@ -102,6 +108,76 @@ func (h *ingestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
102108 h .handler .ServeHTTP (w , r )
103109}
104110
111+ func (h * ingestHandler ) handleHTTPProtobuf (w http.ResponseWriter , r * http.Request ) {
112+ defer r .Body .Close ()
113+
114+ // Read the request body - we need to read it all for protobuf unmarshaling
115+ // Note: Protobuf wire format requires reading the entire message to determine field boundaries
116+ body , err := io .ReadAll (r .Body )
117+ if err != nil {
118+ level .Error (h .log ).Log ("msg" , "failed to read request body" , "err" , err )
119+ http .Error (w , "Failed to read request body" , http .StatusBadRequest )
120+ return
121+ }
122+
123+ // Unmarshal the protobuf request
124+ req := & pprofileotlp.ExportProfilesServiceRequest {}
125+
126+ if r .Header .Get ("Content-Type" ) == "application/json" {
127+ if err := protojson .Unmarshal (body , req ); err != nil {
128+ level .Error (h .log ).Log ("msg" , "failed to unmarshal JSON request" , "err" , err )
129+ http .Error (w , "Failed to parse JSON request" , http .StatusBadRequest )
130+ return
131+ }
132+ } else {
133+ if err := proto .Unmarshal (body , req ); err != nil {
134+ level .Error (h .log ).Log ("msg" , "failed to unmarshal protobuf request" , "err" , err )
135+ http .Error (w , "Failed to parse protobuf request" , http .StatusBadRequest )
136+ return
137+ }
138+ }
139+
140+ ctx := r .Context ()
141+ // Process the request using the existing Export method
142+ // Injects multitenancy info into context if needed
143+ resp , err := h .Export (ctx , req )
144+ if err != nil {
145+ level .Error (h .log ).Log ("msg" , "failed to process profiles" , "err" , err )
146+ // Convert gRPC status to HTTP status
147+ st , ok := status .FromError (err )
148+ if ok {
149+ switch st .Code () {
150+ case codes .InvalidArgument :
151+ http .Error (w , st .Message (), http .StatusBadRequest )
152+ case codes .Unauthenticated :
153+ http .Error (w , st .Message (), http .StatusUnauthorized )
154+ case codes .PermissionDenied :
155+ http .Error (w , st .Message (), http .StatusForbidden )
156+ default :
157+ http .Error (w , st .Message (), http .StatusInternalServerError )
158+ }
159+ } else {
160+ http .Error (w , err .Error (), http .StatusInternalServerError )
161+ }
162+ return
163+ }
164+
165+ // Marshal the response
166+ respBytes , err := proto .Marshal (resp )
167+ if err != nil {
168+ level .Error (h .log ).Log ("msg" , "failed to marshal response" , "err" , err )
169+ http .Error (w , "Failed to marshal response" , http .StatusInternalServerError )
170+ return
171+ }
172+
173+ // Write the response
174+ w .Header ().Set ("Content-Type" , "application/x-protobuf" )
175+ w .WriteHeader (http .StatusOK )
176+ if _ , err := w .Write (respBytes ); err != nil {
177+ level .Error (h .log ).Log ("msg" , "failed to write response" , "err" , err )
178+ }
179+ }
180+
105181func (h * ingestHandler ) Export (ctx context.Context , er * pprofileotlp.ExportProfilesServiceRequest ) (* pprofileotlp.ExportProfilesServiceResponse , error ) {
106182 // TODO: @petethepig This logic is copied from util.AuthenticateUser and should be refactored into a common function
107183 // Extracts user ID from the request metadata and returns and injects the user ID in the context
0 commit comments