@@ -169,27 +169,40 @@ func performRequestValidationForErrorHandler(next http.Handler, w http.ResponseW
169169 errorHandler (w , err .Error (), statusCode )
170170}
171171
172- func makeRequestForValidation (r * http.Request , options * Options ) * http.Request {
172+ // withPrefixStripped temporarily strips the configured prefix from the
173+ // request's path fields, calls fn, then restores the original values.
174+ // This avoids cloning the request (which shallow-copies the Body and
175+ // causes it to be consumed during validation, leaving the handler with
176+ // an empty body — see https://github.com/oapi-codegen/nethttp-middleware/issues/69).
177+ func withPrefixStripped (r * http.Request , options * Options , fn func ()) {
173178 if options == nil || options .Prefix == "" {
174- return r
179+ fn ()
180+ return
175181 }
176182
177183 // Only strip the prefix when it matches on a path segment boundary:
178184 // the path must equal the prefix exactly, or the character immediately
179185 // after the prefix must be '/'.
180186 if ! hasPathPrefix (r .URL .Path , options .Prefix ) {
181- return r
187+ fn ()
188+ return
182189 }
183190
184- r = r .Clone (r .Context ())
191+ origRequestURI := r .RequestURI
192+ origPath := r .URL .Path
193+ origRawPath := r .URL .RawPath
185194
186195 r .RequestURI = stripPrefix (r .RequestURI , options .Prefix )
187196 r .URL .Path = stripPrefix (r .URL .Path , options .Prefix )
188197 if r .URL .RawPath != "" {
189198 r .URL .RawPath = stripPrefix (r .URL .RawPath , options .Prefix )
190199 }
191200
192- return r
201+ fn ()
202+
203+ r .RequestURI = origRequestURI
204+ r .URL .Path = origPath
205+ r .URL .RawPath = origRawPath
193206}
194207
195208// hasPathPrefix reports whether path starts with prefix on a segment boundary.
@@ -210,19 +223,39 @@ func stripPrefix(s, prefix string) string {
210223
211224// Note that this is an inline-and-modified version of `validateRequest`, with a simplified control flow and providing full access to the `error` for the `ErrorHandlerWithOpts` function.
212225func performRequestValidationForErrorHandlerWithOpts (next http.Handler , w http.ResponseWriter , r * http.Request , router routers.Router , options * Options ) {
213- // Build a (possibly prefix-stripped) request for validation, but keep
214- // the original so the downstream handler sees the un-modified path.
215- validationReq := makeRequestForValidation (r , options )
226+ var route * routers.Route
227+ var pathParams map [string ]string
228+ var validationErr error
229+
230+ // Temporarily strip the prefix for route finding and validation,
231+ // then restore it so the handler and error handler see the original path.
232+ withPrefixStripped (r , options , func () {
233+ var err error
234+ route , pathParams , err = router .FindRoute (r )
235+ if err != nil {
236+ validationErr = err
237+ return
238+ }
216239
217- // Find route
218- route , pathParams , err := router .FindRoute (validationReq )
219- if err != nil {
240+ requestValidationInput := & openapi3filter.RequestValidationInput {
241+ Request : r ,
242+ PathParams : pathParams ,
243+ Route : route ,
244+ }
245+
246+ if options != nil {
247+ requestValidationInput .Options = & options .Options
248+ }
249+
250+ validationErr = openapi3filter .ValidateRequest (r .Context (), requestValidationInput )
251+ })
252+
253+ // Route not found
254+ if route == nil && validationErr != nil {
220255 errOpts := ErrorHandlerOpts {
221- // MatchedRoute will be nil, as we've not matched a route we know about
222256 StatusCode : http .StatusNotFound ,
223257 }
224-
225- options .ErrorHandlerWithOpts (r .Context (), err , w , r , errOpts )
258+ options .ErrorHandlerWithOpts (r .Context (), validationErr , w , r , errOpts )
226259 return
227260 }
228261
@@ -231,44 +264,26 @@ func performRequestValidationForErrorHandlerWithOpts(next http.Handler, w http.R
231264 Route : route ,
232265 PathParams : pathParams ,
233266 },
234- // other options will be added before executing
235267 }
236268
237- // Validate request
238- requestValidationInput := & openapi3filter.RequestValidationInput {
239- Request : validationReq ,
240- PathParams : pathParams ,
241- Route : route ,
242- }
243-
244- if options != nil {
245- requestValidationInput .Options = & options .Options
246- }
247-
248- err = openapi3filter .ValidateRequest (validationReq .Context (), requestValidationInput )
249- if err == nil {
250- // it's a valid request, so serve it with the original request
269+ if validationErr == nil {
251270 next .ServeHTTP (w , r )
252271 return
253272 }
254273
255274 var theErr error
256275
257- switch e := err .(type ) {
276+ switch e := validationErr .(type ) {
258277 case openapi3.MultiError :
259278 theErr = e
260279 errOpts .StatusCode = determineStatusCodeForMultiError (e )
261280 case * openapi3filter.RequestError :
262- // We've got a bad request
263281 theErr = e
264282 errOpts .StatusCode = http .StatusBadRequest
265283 case * openapi3filter.SecurityRequirementsError :
266284 theErr = e
267285 errOpts .StatusCode = http .StatusUnauthorized
268286 default :
269- // This should never happen today, but if our upstream code changes,
270- // we don't want to crash the server, so handle the unexpected error.
271- // return http.StatusInternalServerError,
272287 theErr = fmt .Errorf ("error validating route: %w" , e )
273288 errOpts .StatusCode = http .StatusInternalServerError
274289 }
@@ -279,8 +294,19 @@ func performRequestValidationForErrorHandlerWithOpts(next http.Handler, w http.R
279294// validateRequest is called from the middleware above and actually does the work
280295// of validating a request.
281296func validateRequest (r * http.Request , router routers.Router , options * Options ) (int , error ) {
282- r = makeRequestForValidation (r , options )
297+ var statusCode int
298+ var validationErr error
299+
300+ withPrefixStripped (r , options , func () {
301+ statusCode , validationErr = doValidateRequest (r , router , options )
302+ })
303+
304+ return statusCode , validationErr
305+ }
283306
307+ // doValidateRequest performs the actual validation, called within
308+ // withPrefixStripped so the prefix is already removed from r's path.
309+ func doValidateRequest (r * http.Request , router routers.Router , options * Options ) (int , error ) {
284310 // Find route
285311 route , pathParams , err := router .FindRoute (r )
286312 if err != nil {
0 commit comments