Closed bulletblp4 closed 4 years ago
This issue does not seem to follow the issue template. Make sure you provide all the required information.
Start by taking a look at the ProxyConnectHeader
field in http.Transport
. You can set custom headers on proxy CONNECT requests by configuring this property.
If that's not what you want, you should be able to customize the RoundTripper
or the Proxy
to do what you want. Here's an example:
func customProxy(fixedURL *url.URL) func(*http.Request) (*url.URL, error) {
return func(req *http.Request) (*url.URL, error) {
req.Header.Add("My-Header", "value")
return http.ProxyURL(fixedURL)
}
}
myClient := &http.Client{Transport: &http.Transport{Proxy: customProxy(proxyUrl)}}
The SDK doesn't provide any APIs for customizing HTTP headers. The APNSConfig.Headers
field only sets APNS headers for the notification, not HTTP headers.
So the client when calling the method client.Send
doesn't seem to be picking up the proxy.
I get an error "unknown error while making an http call: Post https://fcm.googleapis.com/v1/projects/api-XXX/messages:send: Bad Request"
where as it should be picking up the proxy-url (http://fcm-proxy:8080/v1/projects/api-XXX/messages:send
) I believe. I am doing a similar thing for my apns libs and those seems to call the apns proxy.
func initFCMClient(projectId string) (c *messaging.Client, err error) {
transport := &http.Transport{
Proxy: func(request *http.Request) (*url.URL, error) {
request.Header.Add("HOST","fcm.googleapis.com")
return url.Parse("http://fcm-proxy:8080/v1")
},
IdleConnTimeout: 60 * time.Second,
}
config := &firebase.Config{ProjectID: "api-XXX"}
myClient := &http.Client{Transport: transport}
oClient := option.WithHTTPClient(myClient)
oAuth := option.WithAPIKey("secretkey")
app, err := firebase.NewApp(context.Background(), config, oClient, oAuth)
if err != nil {
return nil, err
}
background := context.Background()
c, err = app.Messaging(background)
if err != nil {
return nil, err
}
return c, err
}
The proxy URL is not visible to the SDK, so it will never get logged in an error message raised by our APIs. Your issue is probably due to bad authentication.
oAuth := option.WithAPIKey("secretkey")
FCM v1 API do not support API key authentication. You must provide valid OAuth2 credentials.
PS: You can also easily verify whether your custom proxy URL is picked up by adding some logging code to your custom Proxy function.
Spent a bit more time on this, and it actually looks like an issue with the proxy code. I'm experimenting with the following example:
transport := &http.Transport{
MaxConnsPerHost: 10,
Proxy: func(request *http.Request) (*url.URL, error) {
log.Printf("using proxy for %s", request.Host)
request.Header.Add("Host", request.Host)
return url.Parse("http://10.0.0.203:9001")
},
}
myClient := &http.Client{Transport: transport}
req, err := http.NewRequestWithContext(ctx, "GET", "http://jsonplaceholder.typicode.com/posts/1", nil)
if err != nil {
log.Fatal(err)
}
resp, err := myClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
log.Println(resp.StatusCode, string(body))
This works as long as the request URL scheme is http
. As soon as I switch to https
, I get the above Not Found
error (which seems to be a low-level networking error rather than an HTTP 404). So it essentially boils down to above code failing to proxy HTTPS requests over an HTTP connection.
I'm not an expert in this area of Go. Hopefully somebody can point out a solution that works.
Turned out the problem was actually in my mock proxy server (I was using a simple reverse proxy script that couldn't handle HTTP to HTTPS bridging properly). Once I used an actual CONNECT proxy server (e.g. https://github.com/elazarl/goproxy), everything worked out fine. Here's my working example:
func main() {
transport := &http.Transport{
Proxy: func(request *http.Request) (*url.URL, error) {
log.Printf("using proxy for %s", request.Host)
return url.Parse("http://10.0.0.203:8080")
},
}
cred, err := google.FindDefaultCredentials(
context.Background(),
"https://www.googleapis.com/auth/cloud-platform")
if err != nil {
log.Fatal(err)
}
trp := &oauth2.Transport{
Base: transport,
Source: cred.TokenSource,
}
myClient := &http.Client{Transport: trp}
oClient := option.WithHTTPClient(myClient)
app, err := firebase.NewApp(context.Background(), nil, oClient)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
c, err := app.Messaging(ctx)
if err != nil {
log.Fatal(err)
}
m := &messaging.Message{Topic: "test"}
r, err := c.Send(ctx, m)
if err != nil {
log.Fatal(err)
}
log.Print(r)
}
Note that I'm using oauth2.Transport
as a wrapper to inject credentials. Without it FCM request will fail with an HTTP 401 error. I didn't have to mess with the Host
header in my proxy, as the Go http implementation handled the proxy protocol correctly.
Since you're attempting to manipulate the Host
header on the request, I suspect you're trying to send the request via a reverse proxy. This is different from a regular HTTP proxy. To get the request to pass through a reverse proxy, here's what I had to do:
type addHeader struct {
host string
delegate http.RoundTripper
}
func (ah *addHeader) RoundTrip(req *http.Request) (*http.Response, error) {
req.Host = ah.host
return ah.delegate.RoundTrip(req)
}
func main() {
cred, err := google.FindDefaultCredentials(
context.Background(),
"https://www.googleapis.com/auth/cloud-platform")
if err != nil {
log.Fatal(err)
}
trp := &oauth2.Transport{
Base: &http.Transport{},
Source: cred.TokenSource,
}
myClient := &http.Client{
Transport: &addHeader{
delegate: trp,
host: "fcm.googleapis.com",
},
}
oClient := option.WithHTTPClient(myClient)
ep := option.WithEndpoint("http://localhost:9001/v1")
app, err := firebase.NewApp(context.Background(), nil, oClient, ep)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
c, err := app.Messaging(ctx)
if err != nil {
log.Fatal(err)
}
m := &messaging.Message{Topic: "test"}
r, err := c.Send(ctx, m)
if err != nil {
log.Fatal(err)
}
log.Print(r)
}
I'm using a custom RoundTripper
to set the Host
header on the request, and using option.WithEndpoint()
to point to the reverse proxy.
Thank you ! that worked...
I'm going to close this based on the information provided in my previous comments. Most developers should be able to setup proxy settings via environment variables. For more niche use cases one can use a combination of http
and options
packages as shown above. I wish there was a way to do options.WithHttpProxy()
or options.WithTransport()
instead of options.HTTPClient()
. But that should be a feature request for the options package.
[REQUIRED] Step 2: Describe your environment
[REQUIRED] Step 3: Describe the problem
I need to access the FCM endpoint via a proxy, which also need an header to be passed. My app currently makes 2 calls via different proxy, hence when I add the env var as
HTTPS_PROXY
in my go app it slows down the other request.I need to figure out a way to call fcm via proxy url and and its header. Currently in the options package (while creating the client) I dont see a way to pass custom header nor do I see that option in
message.AndroidConig
similar to what we have formessage.APNSConfig.Headers
Can we add an api to pass custom header if there isnt one already
Steps to reproduce:
I am creating a custom client and passing API and httpClient as opts. And Proxy is wrapped in the httpClient Passed in the opts
Relevant Code: