英文:
cannot connect graphql-ws to gqlgen
问题
我正在使用gqlgen作为我的服务,apollo client和graphql-ws作为我的前端,并且我正在尝试使用订阅。在我的playground中,它完美地工作,但是当我尝试将客户端连接到它时,我收到以下错误消息:
WebSocket连接到'ws://localhost:8080/'失败:
在我的容器日志中,我收到以下消息:
无法将*http.response升级为websocket websocket:request origin not allowed by Upgrader.CheckOrigin:http:superfluous response.WriteHeader call from github.com/99designs/gqlgen/graphql/handler/transport.SendError(error.go:15)
这是我的Golang代码:
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading environment variables file")
}
port := helpers.Env("PORT")
if port == "" {
port = defaultPort
}
router := chi.NewRouter()
router.Use(cors.New(cors.Options{
AllowedOrigins: strings.Split(helpers.Env("ALLOWED_ORIGINS"), ","),
AllowCredentials: true,
Debug: helpers.Env("DEBUG") == "true",
AllowedHeaders: []string{"*"},
}).Handler)
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
srv.AddTransport(transport.POST{})
upgrader := &transport.Websocket{
Upgrader: websocket.Upgrader{
HandshakeTimeout: 1 * time.Minute,
CheckOrigin: func(r *http.Request) bool {
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
KeepAlivePingInterval: 10 * time.Second,
}
srv.AddTransport(upgrader)
srv.Use(extension.Introspection{})
if helpers.Env("MODE") == "PRODUCTION" {
cache, err := apq.NewCache(24 * time.Hour)
if err != nil {
log.Fatalf("cannot create APQ redis cache: %v", err)
}
srv.Use(extension.AutomaticPersistedQuery{Cache: cache})
}
go initWorkers()
go runAsynqmon()
router.Use(getHeadersMiddleware())
router.Handle("/", srv)
if helpers.Env("MODE") == "DEVELOPMENT" {
router.Handle("/playground", playground.Handler("GraphQL playground", "/"))
log.Printf("connect to http://localhost:%s/playground for GraphQL playground", port)
}
log.Fatal(http.ListenAndServe(":"+port, router))
这是我的客户端代码:
import { ApolloClient, InMemoryCache, from, split } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createUploadLink } from 'apollo-upload-client'
import { createClient } from 'graphql-ws'
import { logout } from '../helpers/logout'
import { getTokenFromStorage } from '../helpers/userData'
import { lang } from '../localization'
const authLink = setContext((_, { headers }) => {
const token = getTokenFromStorage()
return {
headers: {
authorization: token ? `Bearer ${token}` : undefined,
'Accept-Language': lang,
...headers
}
}
})
const httpLink = createUploadLink({
uri: process.env.REACT_APP_GRAPH_BFF || 'http://localhost:8080'
})
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:8080/'
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const logoutLink = onError(({ response }) => {
if (
response?.errors &&
response.errors.length > 0 &&
response.errors.some((errorItem) =>
errorItem.message.toLowerCase().includes('unauthenticated')
)
) {
logout()
}
})
const chainList = [logoutLink, authLink, splitLink]
const linkChain = from(chainList)
const apolloClient = new ApolloClient({
cache: new InMemoryCache({
addTypename: false
}),
link: linkChain
})
export default apolloClient
我认为在upgrader中的CheckOrigin应该可以解决这个问题,但它没有起作用。有什么办法可以解决这个问题吗?
英文:
I'm using gqlgen for my service and apollo client and graphql-ws for my front end and I'm trying to use a subscription and it perfectly works in my playground but when i try to connect the client to it I receive this error:
> WebSocket connection to 'ws://localhost:8080/' failed:
and in my container log I receive:
> unable to upgrade *http.response to websocket websocket: request origin not allowed by Upgrader.CheckOrigin:
http: superfluous response.WriteHeader call from github.com/99designs/gqlgen/graphql/handler/transport.SendError (error.go:15)
here is my golang code:
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading environment variables file")
}
port := helpers.Env("PORT")
if port == "" {
port = defaultPort
}
router := chi.NewRouter()
router.Use(cors.New(cors.Options{
AllowedOrigins: strings.Split(helpers.Env("ALLOWED_ORIGINS"), ","),
AllowCredentials: true,
Debug: helpers.Env("DEBUG") == "true",
AllowedHeaders: []string{"*"},
}).Handler)
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
srv.AddTransport(transport.POST{})
upgrader := &transport.Websocket{
Upgrader: websocket.Upgrader{
HandshakeTimeout: 1 * time.Minute,
CheckOrigin: func(r *http.Request) bool {
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
KeepAlivePingInterval: 10 * time.Second,
}
srv.AddTransport(upgrader)
srv.Use(extension.Introspection{})
if helpers.Env("MODE") == "PRODUCTION" {
cache, err := apq.NewCache(24 * time.Hour)
if err != nil {
log.Fatalf("cannot create APQ redis cache: %v", err)
}
srv.Use(extension.AutomaticPersistedQuery{Cache: cache})
}
go initWorkers()
go runAsynqmon()
router.Use(getHeadersMiddleware())
router.Handle("/", srv)
if helpers.Env("MODE") == "DEVELOPMENT" {
router.Handle("/playground", playground.Handler("GraphQL playground", "/"))
log.Printf("connect to http://localhost:%s/playground for GraphQL playground", port)
}
log.Fatal(http.ListenAndServe(":"+port, router))
and here is my client code:
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createUploadLink } from 'apollo-upload-client'
import { createClient } from 'graphql-ws'
import { logout } from '../helpers/logout'
import { getTokenFromStorage } from '../helpers/userData'
import { lang } from '../localization'
const authLink = setContext((_, { headers }) => {
const token = getTokenFromStorage()
return {
headers: {
authorization: token ? `Bearer ${token}` : undefined,
'Accept-Language': lang,
...headers
}
}
})
const httpLink = createUploadLink({
uri: process.env.REACT_APP_GRAPH_BFF || 'http://localhost:8080'
})
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:8080/'
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const logoutLink = onError(({ response }) => {
if (
response?.errors &&
response.errors.length > 0 &&
response.errors.some((errorItem) =>
errorItem.message.toLowerCase().includes('unauthenticated')
)
) {
logout()
}
})
const chainList = [logoutLink, authLink, splitLink]
const linkChain = from(chainList)
const apolloClient = new ApolloClient({
cache: new InMemoryCache({
addTypename: false
}),
link: linkChain
})
export default apolloClient
I thought CheckOrigin in upgrader would fix this but it didn't work
any idea how to fix this?
答案1
得分: 2
那是一个很棒的问题。这意味着你是一个有才华的程序员...回答在这里:
你需要修改这一行代码:
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
因为NewDefaultServer使用相同的源,这意味着你的Origin和Host必须相同,也就是说你的代码:
CheckOrigin: func(r *http.Request) bool {
return true;
}
没有起作用。
你必须将NewDefaultServer改为New,所以你的代码改为:
srv := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
你的WebSocket正常工作。
注意这个改变,因为NewDefaultServer内部有一些配置用于更新或获取其他一些东西(请参阅包本身),你必须在你的代码中进行适当的编写。
英文:
that was a awesome question. that means you are a talented programmer... and response is here:
you need to change this line:
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
because NewDefaultServer use same origin that means your Origin and Host MUST be same and that means your code :
CheckOrigin: func(r *http.Request) bool {
return true
},
did not work.
you must change NewDefaultServer to New, so your code change to it:
srv := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
and Your WebSocket works fine.
be careful about this change because NewDefaultServer has some config inside it for Update or get or some other things. (see package itself)
and you must write inside it handy in your code.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论