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

Add proxy option to package.json #282

Merged
merged 1 commit into from
Aug 4, 2016
Merged

Add proxy option to package.json #282

merged 1 commit into from
Aug 4, 2016

Conversation

gaearon
Copy link
Contributor

@gaearon gaearon commented Jul 29, 2016

(The proposal was updated since the original posting.)

This is a proof of concept of solution to #147.
It works for simple cases, and I think it’s fairly unobtrusive.

It lets you add a field to package.json like this:

  "proxy": "http://localhost:3001",

Then any unrecognized requests (i.e. aren’t request to bundled assets) will be forwarded to that base URL. This is very similar to how our existing /index.html fallback works for client side routing.

There is one gotcha: of course /index.html fallback and proxy (when specified) would compete. I resolved this with a heuristic that I think would work in the vast majority of cases.

If proxy is specified, we make /index.html fallback a little bit less aggressive, and we only activate it if specifically text/html is present in the accept header. In practice every modern browser (not IE8 but we don’t support it anyway) sends it when you navigate to a page, so /index.html fallback will keep on working just fine for client-side apps. However, if you use fetch(), $.ajax(), axios() or XMLHttpRequest without explicitly putting text/html there, we will happily proxy your request to devProxy.

To illustrate this:

  Open /todos/completed in browser
    * browser sends "text/html" as part of "accepts" header
    * we know that we want to redirect this to /index.html
    * client side router takes over

  fetch('/api/todos/completed')
    * fetch (or alternatives) don't send "text/html" as part of "accepts" header by default
    * devProxy was specified so we don't want to redirect this to /index.html
    * instead, we forward this to devProxy

When proxy is not specified, /index.html fallback kicks in every time, just like now. So this behavior is completely opt-in.

If for some reason the heuristic fails for your project (maybe you just have to fetch() some HTML... templates?), well, you can’t use proxy. It is only a convenience. Please use the regular method (which is not that hard anyway—you’d have to add CORS to your server and hardcode the server URL into your client, just like you would do now anyway).

I feel this could be a good compromise that lets people get started without worrying about CORS.

@alexzherdev
Copy link
Contributor

I think earlier you mentioned the intent to avoid any 'webpackisms' - this doesn't seem to be one as there are likely to be similar solutions in any alternative ecosystems, if I understand correctly?

@gaearon
Copy link
Contributor Author

gaearon commented Jul 29, 2016

Yea, this would work with any Express server.

@gaearon
Copy link
Contributor Author

gaearon commented Jul 29, 2016

Note that this is somewhat limited but fixes what I think will be the most common use case. As an example of a limitation, you can’t proxy /api to / or /myapi of another host—all paths are proxied verbatim. /api here is /api there. But I think it’s fine (and also easier to understand and configure).

@gaearon gaearon added this to the 0.3.0 milestone Jul 29, 2016
@ghost ghost added the CLA Signed label Jul 29, 2016
@lacker
Copy link
Contributor

lacker commented Jul 29, 2016

I like that it matches http-proxy-middleware - that makes us not really dependent on any particular dev server.

Could we just call the field in package.json proxy? I think that is a closer match to what people will be looking for than devBackend.

@lacker
Copy link
Contributor

lacker commented Jul 29, 2016

Also, it's probably useful to distinguish in examples:

http://localhost:4000/api will proxy only that exact URL
http://localhost:4000/api/** will proxy every URL beginning with /api/

I suspect for most use cases people will want to use something ending with **.

Also it's good to be clear that this does not proxy / or paths under /static/** even if your pattern matches those.

Is this proxying websockets as well? It probably should

@gaearon
Copy link
Contributor Author

gaearon commented Jul 29, 2016

Updated the original post with the new proposal.

@gaearon gaearon changed the title Enable proxies via devBackend option in package.json Add devProxy option to package.json Jul 29, 2016
@ghost ghost added the CLA Signed label Jul 30, 2016
@lacker
Copy link
Contributor

lacker commented Jul 30, 2016

So if someone specifies just http://localhost:4000 or http://localhost:4000/ as their devProxy, that's basically always an error, right? Because it will only proxy the root URL, but the root URL is handled by the dev server anyway, so it ends up proxying nothing.

If my understanding is correct there, I bet this becomes a common source of errors, people specifying the proxy without any path and then being confused when it proxies nothing. So maybe you could make it an error when a proxy is specified like that.

Also, just in terms of naming, you like devProxy better than proxy? Because there isn't really any other proxy that will be configured from this package.json... and webpack just calls it proxy... just throwing it out there.

@gaearon
Copy link
Contributor Author

gaearon commented Jul 30, 2016

Because it will only proxy the root URL, but the root URL is handled by the dev server anyway, so it ends up proxying nothing.

No, it proxies any unrecognised request with the new proposal. Actually in 99% cases this is exactly what you want to specify.

@gaearon
Copy link
Contributor Author

gaearon commented Jul 30, 2016

So, to be clear, you almost always want to specify it like http://localhost:4000 with new proposal. It does not use any shorthands now, it's just "what we should prepend the pathname with if the request was unhandled by dev server".

@gaearon
Copy link
Contributor Author

gaearon commented Jul 30, 2016

I'm generally okay with calling it proxy. I thought it might be a bit confusing to squat a top level entry with such generic name but maybe it's fine since it's not a Node app anyway and will not be used for libraries.

@gaearon
Copy link
Contributor Author

gaearon commented Jul 30, 2016

Also new proposal doesn't support any expansions like *. It is solely used for specifying the target now, not matching the source. The source is automatic: we proxy anything we couldn't match, just like described in the algorithm above.

}

// Otherwise, if devProxy is specified, we will let it handle any request
// that isn't /index.html or /sockjs-node/* (used for hot reloading).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we let the proxy handle a request for /? I think the request from a browser will not actually be for /index.html most of the time, it will be for /, which makes some of these comments a bit confusing. I think the behavior is actually the same between /index.html and / though.

@lacker
Copy link
Contributor

lacker commented Jul 30, 2016

Ah I missed how much you had updated the proposal. I'm not familiar with configurations needing the /index.html fallback, but I think from the point of view of proxying a single backend API server, this should work fine. You can't proxy to multiple places but there are several not-too-bothersome workarounds there.

A couple questions around edge cases. Can you proxy to https:? Is the http:// part of the config actually necessary? And, specifying something like http://localhost:4000/api is just invalid now, right? It would be nice to cleanly error on things that are invalid.

Will this proxy websockets?

@gaearon
Copy link
Contributor Author

gaearon commented Jul 30, 2016

I'm not familiar with configurations needing the /index.html fallback

It is necessary for any app with client side routing. If you use HTML5 history APIs you can update URL on the client without actually navigating. So user may end up on e.g. localhost:3000/todos/completed even though such folders don't exist—this is just an arbitrary path specified by the app. Now if user hits refresh, they would get 404 unless we have a fallback for /index.html. In production, such apps work the same way: backend typically serves fallback for unrecognised routes, and it's up to the client to display 404 if client side router doesn't recognise this.

Can you proxy to https:?

I think so but I'm not sure how to verify. I'll try changing that server example to serve over https.

specifying something like http://localhost:4000/api is just invalid now, right?

It is valid, it would just mean that /lol becomes http://localhost:4000/api/lol. I'm not sure how useful that is but I don't see harm in allowing this.

Will this proxy websockets?

It should (I passed an option for that) but need to verify.

@ghost ghost added the CLA Signed label Jul 30, 2016
@ghost ghost added the CLA Signed label Jul 30, 2016
@ghost ghost added the CLA Signed label Jul 31, 2016
@gaearon gaearon changed the title Add devProxy option to package.json Add proxy option to package.json Aug 1, 2016
@gaearon
Copy link
Contributor Author

gaearon commented Aug 4, 2016

OK, I tested this more.
HTTPS seems to work in my testing.

I couldn’t get WebSockets working so I disabled proxying them completely. They don’t suffer from CORS problems anyway by default so I don’t think this is super valuable. If somebody cares much about supporting them, they can send a PR.

@gaearon gaearon merged commit 379cf5c into master Aug 4, 2016
@gaearon gaearon deleted the proxy branch August 4, 2016 15:50
@gschrader
Copy link

Thank you for adding the proxy feature! So for websocket calls one would point the url directly to the server specified in package.json for development. Is there a suggested way to build the url so that it works for both running under development mode and production? i.e. ws://localhost:4000/api/ws in dev and ws://host:port/api/ws for prod?

I guess is there a way to tell if you're running under dev or production and is there a way to access the proxy value from package.json?

@gaearon
Copy link
Contributor Author

gaearon commented Aug 5, 2016

I guess is there a way to tell if you're running under dev or production

Yes.

Specifically:

By default you will have NODE_ENV defined for you [...] These environment variables will be defined for you on process.env.

if (process.env.NODE_ENV === 'production') {
  // ...
}

is there a way to access the proxy value from package.json?

No.

@gschrader
Copy link

Thanks for clarifying @gaearon, I can work with that.

@fourcolors
Copy link

@gaearon to clarify, is this proxy meant to be on production or just development? I very common use case for proxy is both development and production.

I really want to use create-react-app for production but am still struggling to find an easy straight forward solution for multiple staging environments, proxies, and customizing things like css modules etc.

I'm still a bit new to this project and will contribute after my current project isn't so crazy.

Thanks for the great work

@as-aaron
Copy link

as-aaron commented Oct 11, 2016

This proxy feature is great, but I wasn't able to determine if it is possible to define multiple proxy servers?This is relevant in my case because the authentication and api servers are separate.

@gaearon
Copy link
Contributor Author

gaearon commented Oct 11, 2016

@apet-as I'm afraid this is a more advanced use case, and we won't support it out of the box. You can use custom environment variables for this to determine the endpoints instead in your JS code. The user guide includes information on using custom variables.

@as-aaron
Copy link

@gaearon Thanks for the reply :) I am quite new to this environment(node) and toolset, but I am unsure how that would allow me to solve the issue in this case.

To clarify with a bit more detail I want to proxy requests made to /api/oauth to auth.domain.com and requests made to /api/rest to api.domain.com without restarting the application to swap between them.

I understand that configuring environment variables would allow you to configure what proxy endpoint is used when the server starts but I don't see how I could use that to switch the endpoint dynamically while it is running?

@gaearon
Copy link
Contributor Author

gaearon commented Oct 11, 2016

You can't really proxy anything by yourself solely in the client code. But you could create a single wrapper function that you use for every single request. That function could use specified endpoint in production, but tweak it in development in any way you want.

@as-aaron
Copy link

as-aaron commented Oct 11, 2016

In this case I am talking solely about development, when I am running the react app in localhost, I don't intend to deploy this as is to production.

I am trying to use it as a way to learn, build and test concepts and shared code for other javascript applications that use this api. The api service is not being run locally on my machine so I need to avoid cors issues.

The part I don't understand is how my client code could dynamically tweak the webpack server proxy endpoint on the fly in development.

@as-aaron
Copy link

I understand if this is not really a core use case for the target audience but I am trying to achieve something to the effect of:

"proxy" : {
"/api/oauth" : "https://auth.endpoint.com",
"/api/rest" : "https://api.endpoint.com"
}

@gaearon
Copy link
Contributor Author

gaearon commented Oct 12, 2016

Sorry, as I explained above, if you need to proxy to multiple locations, you can't use the proxy field at all. It just doesn't support this use case in Create React App.

The part I don't understand is how my client code could dynamically tweak the webpack server proxy endpoint on the fly in development.

It can't. My point was that since you can't use the proxy feature then you're left with something like

function myFetch(url, ...args) {
  if (process.env.NODE_ENV === 'development') {
    url = url.replace('/api/oauth', 'https://auth.endpoint.com');
    url = url.replace('/api/rest', 'https://api.endpoint.com');
  }
  return fetch(url, ...args);
}

and then using it throughout your project. This doesn't handle CORS so it doesn't work the same way as proxy but I hope it helps. If you absolutely need to handle CORS your next best bet is to run your own custom proxy script locally, and use proxy field to point to that.

@as-aaron
Copy link

@gaearon Thanks, that is what I figured. Already handling the different endpoints in code fine it is just the CORS issue I was struggling with.

@esseswann
Copy link

@gaearon Can you give me a hint why in my application '/graphql' requests passed through the proxy are transformed from POST to GET and consequently loose body content? I'm using it like this:
"proxy": <ip>

@gaearon
Copy link
Contributor Author

gaearon commented Feb 24, 2017

@esseswann Sorry, I don't know. If you have any problems please create an issue explaining them.

@esseswann
Copy link

@gaearon No probs, I'll create an issue if I manage to reproduce

@lock lock bot locked and limited conversation to collaborators Jan 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants