Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Frontend POST / DELETE calls cause "http: multiple response.WriteHeader calls" warning #861

Closed
danikarik opened this issue Jan 16, 2018 · 10 comments
Milestone

Comments

@danikarik
Copy link

danikarik commented Jan 16, 2018

I created new project with --api flag. Put CORS headers according to #609 comment using CORS Handler. After that Angular front-end sends POST request to "/session" and DELETE to "/session".

In Buffalo logs I'm getting warnings like:

2018/01/16 16:38:42 http: multiple response.WriteHeader calls
INFO[2018-01-16T16:38:42+06:00] /session content_type=application/json duration=228.922342ms form="{}" human_size="21 B" method=POST params="{}" path=/session render="93.109µs" request_id=8f03e03e99-847d6fec21 size=21 status=200

2018/01/16 16:38:55 http: multiple response.WriteHeader calls
INFO[2018-01-16T16:38:55+06:00] /session content_type=application/json duration="58.221µs" form=null human_size="21 B" method=DELETE params="{}" path=/session render="38.818µs" request_id=8f03e03e99-7192595a13 size=21 status=200

func App() *buffalo.App {
	if app == nil {
		app = buffalo.New(buffalo.Options{
			Env:          ENV,
			SessionStore: sessions.NewCookieStore([]byte(SECRET)),
			SessionName:  "sessionid",
		})

		// Set the request content type to JSON
		app.Use(middleware.SetContentType("application/json"))

		if ENV == "development" {
			app.Use(middleware.ParameterLogger)
			app.PreWares = []buffalo.PreWare{cors.New(cors.Options{
				AllowedOrigins:   []string{"*"},
				AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
				AllowedHeaders:   []string{"Content-Type", "Cookie"},
				AllowCredentials: true,
			}).Handler}
		}

		// Set custom error response
		...

		app.GET("/", HomeHandler)

		app.POST("/session", AuthLogin)

		auth := app.Group("/")
		auth.Use(AuthRequired)
		auth.DELETE("/session", AuthLogout)
		auth.GET("/session", AuthSession)
	}

	return app
}

Do I have problems with Prewares and CORS or wrong implementation?

@markbates
Copy link
Member

I don't know the CORS package, so I can't tell you exactly what's going on, but my gut says that CORS is writing headers and then your app is writing headers, hence the multiple header writes. You should investigate why the CORS package is writing headers if it doesn't need to.

@danikarik
Copy link
Author

I've found this WriteHeader:

func (c *Cors) Handler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" {
			c.logf("Handler: Preflight request")
			c.handlePreflight(w, r)
			// Preflight requests are standalone and should stop the chain as some other
			// middleware may not handle OPTIONS requests correctly. One typical example
			// is authentication middleware ; OPTIONS requests won't carry authentication
			// headers (see #1)
			if c.optionPassthrough {
				h.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusOK)
			}
		} else {
			c.logf("Handler: Actual request")
			c.handleActualRequest(w, r)
			h.ServeHTTP(w, r)
		}
	})
}

Can It cause that warning?

@markbates
Copy link
Member

Possibly. I don't know the Cors or how Angular does it's requests, so I really can't answer the question.

@markbates
Copy link
Member

For what it's worth, I tried it locally, everything worked fine for me. It has to be your front-end

// App is where all routes and middleware for buffalo
// should be defined. This is the nerve center of your
// application.
func App() *buffalo.App {
	if app == nil {
		app = buffalo.New(buffalo.Options{
			Env:         ENV,
			SessionName: "_ggjs_session",
			// PreWares:    []buffalo.PreWare{cors.Default().Handler},
		})
		app.PreWares = []buffalo.PreWare{cors.New(cors.Options{
			AllowedOrigins:   []string{"*"},
			AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
			AllowedHeaders:   []string{"Content-Type", "Cookie"},
			AllowCredentials: true,
		}).Handler}
		// Automatically redirect to SSL
		app.Use(ssl.ForceSSL(secure.Options{
			SSLRedirect:     ENV == "production",
			SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
		}))

		if ENV == "development" {
			app.Use(middleware.ParameterLogger)
		}

		// Protect against CSRF attacks. https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
		// Remove to disable this.
		app.Use(csrf.New)

		// Wraps each request in a transaction.
		//  c.Value("tx").(*pop.PopTransaction)
		// Remove to disable this.
		app.Use(middleware.PopTransaction(models.DB))

		// Setup and use translations:
		var err error
		if T, err = i18n.New(packr.NewBox("../locales"), "en-US"); err != nil {
			app.Stop(err)
		}
		app.Use(T.Middleware())

		app.GET("/", HomeHandler)

		// serve files from the public directory:
		app.ServeFiles("/", assetsBox)
	}

	return app
}
$ curl -D - -H 'Origin: http://foo.com' http://localhost:3000/
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://foo.com
Content-Type: text/html
Set-Cookie: _ggjs_session=MTUxNjIwNjU4MXxEdi1CQkFFQ180SUFBUkFCRUFBQU1QLUNBQUVHYzNSeWFXNW5EQTRBREhKbGNYVmxjM1J2Y2w5cFpBWnpkSEpwYm1jTURBQUtOMkk0WkRFellqWXhaZz09fDbV1DvhrMrx010sP5FbVef8_oqgwijYp1HrewN80WVm; Path=/; Expires=Fri, 16 Feb 2018 16:29:41 GMT; Max-Age=2592000
Set-Cookie: _ggjs_session=MTUxNjIwNjU4MXxEdi1CQkFFQ180SUFBUkFCRUFBQV81bl9nZ0FEQm5OMGNtbHVad3dPQUF4eVpYRjFaWE4wYjNKZmFXUUdjM1J5YVc1bkRBd0FDamRpT0dReE0ySTJNV1lHYzNSeWFXNW5EQlFBRW1GMWRHaGxiblJwWTJsMGVWOTBiMnRsYmdkYlhYVnBiblE0Q2lJQUlBbjcxQTVNQnRCeVZDNDBOeVJrNVZrYmpPMmtFRkpSWWFYTHpuRGNKR19xQm5OMGNtbHVad3dKQUFkZlpteGhjMmhmQjF0ZGRXbHVkRGdLQkFBQ2UzMD18pXWc9m4jsrK94be6sQ_ObNtXuRlvSVLwHrPw6vhqL4E=; Path=/; Expires=Fri, 16 Feb 2018 16:29:41 GMT; Max-Age=2592000
Vary: Origin
Date: Wed, 17 Jan 2018 16:29:41 GMT
Content-Length: 673

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Buffalo - Ggjs</title>
    <link href="/assets/application.9a62b727702467b32bd9.css" media="screen" rel="stylesheet" />
    <meta name="csrf-param" content="authenticity_token" />
    <meta name="csrf-token" content="p8nXy8O6dcFDVnmMsNyQioT6uezr4EG9ar6gTOUr1Cs=" />
    <link rel="icon" href="/assets/images/favicon.ico">
  </head>
  <body>

    <div class="container">
      
      hello

    </div>

    <script src="/assets/application.9a62b727702467b32bd9.js" type="text/javascript"></script>
    <script src="/assets/main.9a62b727702467b32bd9.js" type="text/javascript"></script>
  </body>
</html>

@danikarik
Copy link
Author

Problems occurs on preflight request, when you send with OPTIONS method.
req -> OPTIONS -> 200
WARNING between them
req -> POST -> 200

I created test api project to reproduce warning:
buffalo new buffalo-cors-api --api --skip-pop --skip-webpack --with-dep

Test routes:

app.GET("/test/get", TestGetHandler)
app.POST("/test/post", TestPostHandler)
app.DELETE("/test/delete", TestDeleteHandler)

Action:

// TestGetHandler default implementation.
func TestGetHandler(c buffalo.Context) error {
	return c.Render(200, r.JSON(map[string]string{
		"status": "success",
	}))
}

// TestPostHandler default implementation.
func TestPostHandler(c buffalo.Context) error {
	return c.Render(200, r.JSON(map[string]string{
		"status": "success",
	}))
}

// TestDeleteHandler default implementation.
func TestDeleteHandler(c buffalo.Context) error {
	return c.Render(200, r.JSON(map[string]string{
		"status": "success",
	}))
}

POST cURL:

curl -H "Origin: http://foo.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -X OPTIONS --verbose \
  http://localhost:3000/test/post

DELETE cURL:

curl -H "Origin: http://foo.com" \
  -H "Access-Control-Request-Method: DELETE" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -X OPTIONS --verbose \
  http://localhost:3000/test/delete

@danikarik
Copy link
Author

@markbates Can you try this

@markbates
Copy link
Member

I believe you, I don’t have the time unfortunately to try it. If the CORS package is writing headers and then passing it off to the application, that’s a problem. Once someone writes headers others can’t, so if that’s what they’re doing, that’s a problem in that package. You can probably your problem without buffalo and just using a simple net/http app. I don’t believe this to be an issue with buffalo. If you can reproduce it with a basic http app that uses cors and writes headers then the problem is definitely with the cors package.

@markbates
Copy link
Member

I talked to a friend of mine, and he dug through it, and we found the issue! You're not losing your mind. :)

There's a fix in the development branch of Buffalo if you want to try it out and let me know if that fixes it for your apps as well.

@markbates markbates added this to the 0.11.0 milestone Jan 18, 2018
@danikarik
Copy link
Author

@markbates Hi! Yes, fix solved problem!
How soon you will merge master branch? Including #866 ?

@markbates
Copy link
Member

markbates commented Jan 19, 2018 via email

ntakouris pushed a commit to ntakouris/buffalo that referenced this issue Jan 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants