Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uppercase letter in the struct tag makes Hertz header binding weird #1104

Closed
sunziping2016 opened this issue May 6, 2024 · 6 comments
Closed
Assignees

Comments

@sunziping2016
Copy link

sunziping2016 commented May 6, 2024

Describe the bug

Hertz header binding becomes case sensitve since v0.7

To Reproduce

Use the following struct definition

type WebSocketHeaders struct {
	SecWebSocketExtensions []string `header:"Sec-WebSocket-Extensions"`
	SecWebSocketKey        string   `header:"Sec-WebSocket-Key"`
	SecWebSocketProtocol   []string `header:"Sec-WebSocket-Protocol"`
	SecWebSocketVersion    string   `header:"Sec-WebSocket-Version"`
}

Then bind to a request header which use *-Websocket-* instead of *-WebSocket-*.

Only SecWebSocketKey and SecWebSocketVersion (whose type is string) can be successfully bound. SecWebSocketExtensions and SecWebSocketProtocol (whose type is []string) cannot be bound correctly and will be empty no matter what the request sets.

Expected behavior

Successfully binding all the header.

Screenshots

N/A

Hertz version:

v0.7.3 affected. v0.6.* seems not affected.

Environment:

N/A

Additional context

To make it worse, the standard way to spell it is WebSocket not Websocket. But there is a legacy bug with Chromium. See wrong headers for websockets draft-hixie-76 protocol [41183445] - Chromium. If a user decides to support chromium using the binding facilities provided by hertz, other browsers may get broken.

@FGYFFFF
Copy link
Contributor

FGYFFFF commented May 8, 2024

@sunziping2016 can you offer the raw request curl for the header? I will try it

@sunziping2016
Copy link
Author

sunziping2016 commented May 8, 2024

@sunziping2016 can you offer the raw request curl for the header? I will try it

I tried to reproducing the issue. And I found my origin description of this issue inaccurate.

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

type Request struct {
	XSingleValue   string   `header:"X-SingleHead-Value"`
	XMultipleValue []string `header:"X-MultipleHead-Value"`
}

func main() {
	h := server.Default()

	h.GET("/", func(c context.Context, ctx *app.RequestContext) {
		var req Request
		err := ctx.BindAndValidate(&req)
		if err != nil {
			ctx.AbortWithStatusJSON(consts.StatusInternalServerError, utils.H{
				"message": "failed to decode",
			})
		} else {
			ctx.JSON(consts.StatusOK, utils.H{
				"data": req,
			})
		}
	})

	h.Spin()
}

Compile it with the latest version of Hertz.

Then, run curl http://127.0.0.1:8888 -H "X-MultipleHead-Value: hello". You should be able to see {"data":{"XSingleValue":"","XMultipleValue":null}}, whereas the expected output shall be {"data":{"XSingleValue":"","XMultipleValue":["hello"]}}.

However to trigger this issue (or unexpected behavior for me), the struct field tag must contains an uppercase letter in the middle of a word. Change "X-MultipleHead-Value" to "X-Multiplehead-Value" will make hertz behave as expected. Anyway, to trigger this issue, here are some requirements:

  • Hertz >= v0.7
  • []string not string
  • header name: at lease one uppercase letter in the middle of a word

@rogerogers
Copy link
Contributor

HTTP defines that header names are case-insensitive. The request parser implements this by using CanonicalHeaderKey, making the first character and any characters following a hyphen uppercase and the rest lowercase.

So the uppercase letter in the middle is useless.

@rogerogers
Copy link
Contributor

rogerogers commented May 8, 2024

If it must be used this way, perhaps the conversion can be disabled using
ctx.Request.Header.DisableNormalizing()

@sunziping2016
Copy link
Author

HTTP defines that header names are case-insensitive. The request parser implements this by using CanonicalHeaderKey, making the first character and any characters following a hyphen uppercase and the rest lowercase.

So the uppercase letter in the middle is useless.

Thanks! I see. I think this issue's title is no longer accurate, and the behavior of Hertz looks good to me. I'll avoid any uppercase letter in the middle.

However, the inconsistent behavior between []string and string may be confusing to users. Anyway, that's not a big deal.

❯ curl http://127.0.0.1:8888 -H "X-MultipleHeader-Value: hello"
{"data":{"XSingleValue":"","XMultipleValue":null}}⏎
❯ curl http://127.0.0.1:8888 -H "X-SingleHeader-Value: hello"
{"data":{"XSingleValue":"hello","XMultipleValue":null}}⏎

Thanks for your reply. You can close the issue.

@sunziping2016 sunziping2016 changed the title Hertz header binding becomes case sensitve since v0.7 Uppercase letter in the middle makes Hertz header binding weird May 8, 2024
@sunziping2016 sunziping2016 changed the title Uppercase letter in the middle makes Hertz header binding weird Uppercase letter in the struct tag makes Hertz header binding weird May 8, 2024
@FGYFFFF
Copy link
Contributor

FGYFFFF commented May 8, 2024

@sunziping2016 @rogerogers 这个问题我说明一下吧,在重构的时候就已经考虑到了。
重构前:

  • 使用 go-tagexpr 作为默认的绑定器,该绑定器会默认将所有 header tag 做归一化,所以可以看到重构前绑定上值了
  • 但是如果 hertz 关闭了 header 归一化的能力,那么 go-tagexpr 做的归一化 header 就会失效,导致绑定失败

重构后:

  • 我们考虑到用户可能有不同的归一化配置,所以我们没有对 header tag 进默认进行归一化,完全让用户自行写 header tag 的格式来实现与 header 的对齐;这也是导致出现 diff 的原因。
  • 现在看起来,如果我们做归一化的行为跟着 hertz 的配置走的话,可能在一定程度上减少这个问题的干扰。 即如果开了归一化配置,则binder也进行header tag 的归一化,反之则不进行。

@sunziping2016 如果有兴趣,可以提个 pr 再讨论下。代码修改位置: https://github.com/cloudwego/hertz/blob/develop/pkg/app/server/binding/internal/decoder/slice_getter.go#L126

@FGYFFFF FGYFFFF closed this as completed May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants