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

[Question] how to get http.ResponseWriter in services with using middleware? #2953

Closed
z760087139 opened this issue Aug 15, 2023 · 13 comments
Closed
Labels
question Further information is requested

Comments

@z760087139
Copy link

我想在 services 使用 http.ResponseWriter,但如果在 server 层增加中间件,且中间件进行了 context 封装,如例子中的 metadata 中间件,对 context 断言 http.Context 则会失败
有没有办法在使用中间件的同时能够获取 http.ResponseWriter?
有个类似的 #2429 question 但未考虑中间件对ctx 的封装问题
example

package main

import (
	"context"
	"fmt"
	"github.com/go-kratos/examples/helloworld/helloworld"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/middleware/metadata"
	"github.com/go-kratos/kratos/v2/middleware/recovery"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"github.com/go-kratos/kratos/v2/transport/http"
	"log"
)

// go build -ldflags "-X main.Version=x.y.z"
var (
	// Name is the name of the compiled software.
	Name = "helloworld"
	// Version is the version of the compiled software.
	// Version = "v1.0.0"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
	helloworld.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
        // 断言 http.Context,用于获取 http.ResponseWriter
	hctx, ok := ctx.(http.Context)
	if !ok {
		return nil, errors.BadRequest("custom_error", "not a http.Context")
	}
        hctx.Response()
	if in.Name == "error" {
		return nil, errors.BadRequest("custom_error", fmt.Sprintf("invalid argument %s", in.Name))
	}
	if in.Name == "panic" {
		panic("server panic")
	}
	return &helloworld.HelloReply{Message: fmt.Sprintf("Hello %+v", in.Name)}, nil
}

func main() {
	s := &server{}
	httpSrv := http.NewServer(
		http.Address(":8000"),
		http.Middleware(
			recovery.Recovery(),
                        // 增加中间件
			metadata.Server(),
		),
	)
	grpcSrv := grpc.NewServer(
		grpc.Address(":9000"),
		grpc.Middleware(
			recovery.Recovery(),
		),
	)
	helloworld.RegisterGreeterServer(grpcSrv, s)
	helloworld.RegisterGreeterHTTPServer(httpSrv, s)

	app := kratos.New(
		kratos.Name(Name),
		kratos.Server(
			httpSrv,
			grpcSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}
@z760087139 z760087139 added the question Further information is requested label Aug 15, 2023
@guihouchang
Copy link
Contributor

可以尝试在ResponseEncoder这里处理

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


You can try to handle it here in ResponseEncoder

@z760087139
Copy link
Author

ResponseEncoder 只是针对返回的内容进行 encode
如果我希望在 service 获取 responseWriter 并升级成 websocket 呢?
由于现在 kratos 框架并不提供通用的 websocket 入口,我现在想通过中间件提前封装 http.Context 并提供获取方式

package main

import (
	"context"
	"github.com/go-kratos/examples/helloworld/helloworld"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/middleware/metadata"
	"github.com/go-kratos/kratos/v2/middleware/recovery"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"github.com/go-kratos/kratos/v2/transport/http"
	"github.com/gorilla/websocket"
	"log"
)

// go build -ldflags "-X main.Version=x.y.z"
var (
	// Name is the name of the compiled software.
	Name = "helloworld"
	// Version is the version of the compiled software.
	// Version = "v1.0.0"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
	helloworld.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
	hctx, ok := FromContext(ctx)
	if !ok {
		return nil, errors.BadRequest("custom_error", "not a http.Context")
	}
	req := hctx.Request()
	resp := hctx.Response()
	
	upgrader := websocket.Upgrader{}
	c, _ := upgrader.Upgrade(resp, req, nil)
	c.WriteMessage(websocket.TextMessage, []byte("websocket connect"))
	return nil, nil
}

type HttpC struct{}

func FromContext(ctx context.Context) (http.Context, bool) {
	h, ok := ctx.Value(HttpC{}).(http.Context)
	return h, ok
}

func HttpContext(handler middleware.Handler) middleware.Handler {
	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
		ctx = context.WithValue(ctx, HttpC{}, ctx)
		return handler(ctx, req)
	}
}

func main() {
	s := &server{}
	httpSrv := http.NewServer(
		http.Address(":8000"),
		http.Middleware(
			recovery.Recovery(),
			HttpContext,
			metadata.Server(),
		),
	)
	grpcSrv := grpc.NewServer(
		grpc.Address(":9000"),
		grpc.Middleware(
			recovery.Recovery(),
		),
	)
	helloworld.RegisterGreeterServer(grpcSrv, s)
	helloworld.RegisterGreeterHTTPServer(httpSrv, s)

	app := kratos.New(
		kratos.Name(Name),
		kratos.Server(
			httpSrv,
			grpcSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

@XiaoK29
Copy link

XiaoK29 commented Aug 18, 2023

可以使用http.RequestFromServerContext(ctx)来做,这是我写的一个中间件可以参考一下,RequestFromServerContext返回的是你要的东西
image
image

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


You can use http.RequestFromServerContext(ctx) to do it, this is a middleware I wrote, you can refer to it, RequestFromServerContext returns what you want
image
image

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


You can use http.RequestFromServerContext(ctx) to do it. This is a middleware I wrote for reference. RequestFromServerContext returns what you want! [Image](https://user-images.githubusercontent.com/38639105/ 261493303-a47f37e2-dc09-4a7d-b220-21c1e69a4111.png) ![Image](https://user-images.githubusercontent.com/38639105/261493411-d527ed78-b838-4cc0-bc8e-e5cba9b1211 e.png)

Can you please give me the coding of RequestFromServerContext?

@XiaoK29
Copy link

XiaoK29 commented Sep 25, 2023

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿

You can use http.RequestFromServerContext(ctx) to do it. This is a middleware I wrote for reference. RequestFromServerContext returns what you want! [Image](https://user-images.githubusercontent.com/38639105/ 261493303-a47f37e2-dc09-4a7d-b220-21c1e69a4111.png) ![Image](https://user-images.githubusercontent.com/38639105/261493411-d527ed78-b838-4cc0-bc8e-e5cba9b1211 e.png)

Can you please give me the coding of RequestFromServerContext?

func getIP(handler middleware.Handler) middleware.Handler {
	return func(ctx context.Context, req interface{}) (interface{}, error) {
		if httpCtx, ok := http.RequestFromServerContext(ctx); ok {
			host, _, err := net.SplitHostPort(httpCtx.RemoteAddr)
			if err != nil {
				return nil, err
			}

			ctx = context.WithValue(ctx, "ip", host)
			return handler(ctx, req)
		}

		return handler(ctx, req)
	}
}

Copy link

dosubot bot commented Dec 25, 2023

Hi, @z760087139

I'm helping the Kratos team manage their backlog and am marking this issue as stale. It seems like you're seeking a solution to access http.ResponseWriter in services while using middleware that wraps the context, such as the metadata middleware. There have been comments from guihouchang, XiaoK29, and others providing suggestions and code examples to address the issue.

Could you please confirm if this issue is still relevant to the latest version of the Kratos repository? If it is, please let the Kratos team know by commenting on the issue. Otherwise, feel free to close the issue yourself, or it will be automatically closed in 7 days. Thank you!

@dosubot dosubot bot added the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label Dec 25, 2023
@dosubot dosubot bot closed this as not planned Won't fix, can't repro, duplicate, stale Jan 30, 2024
@dosubot dosubot bot removed the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label Jan 30, 2024
@wjfmmyjj
Copy link

可以使用http.RequestFromServerContext(ctx)来做,这是我写的一个中间件可以参考一下,RequestFromServerContext返回的是你要的东西 image image

RequestFromServerContext返回的是request,请问有什么方法可以获取ResponseWriter吗,也是用于websocket的

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


You can use http.RequestFromServerContext(ctx) to do it. This is a middleware I wrote for reference. RequestFromServerContext returns what you want! [image](https://private-user-images.githubusercontent.com /38639105/261493303-a47f37e2-dc09-4a7d-b220-21c1e69a4111.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..xpuwiK2h71ykBqBB5CLBaPzE2x3jtWj9 gKHhdd5fOYw) ![image](https://private-user-images.githubusercontent.com/38639105/261493411-d527ed78-b838 -4cc0-bc8e-e5cba9b1211e.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..7zz3_JbryToNKgVSQvlYUBTa-sfoo6Il9OR7AUCJBvg)

RequestFromServerContext returns request. Is there any way to get ResponseWriter? It is also used for websocket.

@XiaoK29
Copy link

XiaoK29 commented Sep 25, 2024

请问有什么方法可以获取Response

使用这个方法
https://go-kratos.dev/docs/component/transport/http#responseencoderen-encoderesponsefunc-serveroption

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Is there any way to get the Response?

use this method
https://go-kratos.dev/docs/component/transport/http#responseencoderen-encoderesponsefunc-serveroption

@afraidjpg
Copy link

可以使用http.RequestFromServerContext(ctx)来做,这是我写的一个中间件可以参考一下,RequestFromServerContext返回的是你要的东西 image image

RequestFromServerContext返回的是request,请问有什么方法可以获取ResponseWriter吗,也是用于websocket的

I using middleware to store the http.Context type to context fist, and then get that in other place.

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, ss http2.ServiceRegister, wl http2.WhiteList, r redis.Cmdable, logger log.Logger) *http.Server {
	var opts = []http.ServerOption{//...}
	if c.Http.Network != "" {
		opts = append(opts, http.Network(c.Http.Network))
	}
	if c.Http.Addr != "" {
		opts = append(opts, http.Address(c.Http.Addr))
	}
	if c.Http.Timeout != nil {
		opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
	}

        // use middleware.Chain to ensure that middleware are called in order
        // make sure to call SaveKratosContext before any middleware that might call "context.WithContext()"
	chain := middleware.Chain(recovery.Recovery(), http2.SaveKratosContext() // ... other middleware)
	opts = append(opts, http.Middleware(chain))

	srv := http.NewServer(opts...)

	ss.RegisterHTTP(srv)
	return srv
}

middleware:

package http

import (
	"context"
	"github.com/go-kratos/kratos/v2/middleware"
	http2 "github.com/go-kratos/kratos/v2/transport/http"
)

func SaveKratosContext() middleware.Middleware {
	return func(next middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			if c, ok := ctx.(http2.Context); ok {
				ctx = context.WithValue(ctx, "kratos_http_context", c)
			}

			return next(ctx, req)
		}
	}
}

func KratosContextFromContext(ctx context.Context) http2.Context {
	if c, ok := ctx.Value("kratos_http_context").(http2.Context); ok {
		return c
	}
	return nil
}

How to use:

func WriteDataToResponse(ctx context.Context, v interface{}) {
	serverCtx := KratosContextFromContext(ctx)
	if serverCtx == nil {
		fmt.Println("type assertion failed")
		return
	}
        var yourReply []byte()
	serverCtx.Response().Write(yourReply)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

6 participants