diff --git a/README.md b/README.md index b6b350d..a5e3b5d 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ python-react provides an interface to a render server which is capable of render from your python process. Render requests should provide a path to a JS file that exports a React component. If you want to pass -data to the component, you can optionally provide a second argument that will be used as the component's +data to the component, you can optionally provide a second argument that will be used as the component's `props` property. ```python @@ -77,10 +77,10 @@ If the object is coerced to a string, it will emit the value of the `markup` att Render servers are typically Node.js processes which sit alongside the python process and respond to network requests. -To add a render server to your project, you can refer to the [basic rendering example](examples/basic_rendering) -for a simple server that will cover most cases. The key files for the render server are: +To add a render server to your project, you can refer to the [basic rendering example](examples/basic_rendering) +for a simple server that will cover most cases. The key files for the render server are: - [render_server.js](examples/basic_rendering/render_server.js) - the server's source code - - [package.json](examples/basic_rendering/package.json) - the server's dependencies, installable with + - [package.json](examples/basic_rendering/package.json) - the server's dependencies, installable with [npm](http://npmjs.com) @@ -92,13 +92,13 @@ setup involves a build tool and a python package that can integrate it. The two most popular build tools are: -- [Webpack](https://webpack.github.io) - compiles your files into browser-executable code and provides a +- [Webpack](https://webpack.github.io) - compiles your files into browser-executable code and provides a variety of tools and processes which can simplify complicated workflows. -- [Browserify](http://browserify.org/) - has a lot of cross-over with webpack. Is argurably the easiest of the +- [Browserify](http://browserify.org/) - has a lot of cross-over with webpack. Is argurably the easiest of the two to use, but it tends to lag behind webpack in functionality. -For React projects, you'll find that webpack is the usual recommendation. Webpack's hot module replacement, -code-splitting, and a wealth of loaders are the features typically cited as being irreplaceable. +For React projects, you'll find that webpack is the usual recommendation. Webpack's hot module replacement, +code-splitting, and a wealth of loaders are the features typically cited as being irreplaceable. [react-hot-loader](https://github.com/gaearon/react-hot-loader) is a particularly useful tool, as it allows changes to your components to be streamed live into your browser. @@ -121,8 +121,8 @@ javascript worlds. render_component ---------------- -Renders a component to its initial HTML. You can use this method to generate HTML on the server -and send the markup down on the initial request for faster page loads and to allow search engines +Renders a component to its initial HTML. You can use this method to generate HTML on the server +and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. @@ -147,6 +147,12 @@ render_component( # An optional object which will be used instead of the default renderer renderer=None, + + # An optional dictionary of request header information (such as `Accept-Language`) + # to pass along with the request to the render server + request_headers={ + 'Accept-Language': 'da, en-gb;q=0.8, en;q=0.7' + }, ) ``` @@ -156,25 +162,25 @@ via Django's static file finders. By default, render_component relies on access to a render server that exposes an endpoint compatible with [react-render's API](https://github.com/markfinger/react-render). If you want to use a different renderer, pass in an object as the `renderer` arg. The object should expose a `render` method which -accepts the `path`, `data`, and `to_static_markup` arguments. +accepts the `path`, `data`, `to_static_markup`, and `request_headers` arguments. Render server ------------- Earlier versions of this library would run the render server as a subprocess, this tended to make development -easier, but introduced instabilities and opaque behaviour. To avoid these issues python-react now relies on +easier, but introduced instabilities and opaque behaviour. To avoid these issues python-react now relies on externally managed process. While managing extra processes can add more overhead initially, it avoids pain down the track. If you only want to run the render server in particular environments, change the `RENDER` setting to -False. When `RENDER` is False, the render server is not used directly, but it's wrapper will return similar +False. When `RENDER` is False, the render server is not used directly, but it's wrapper will return similar objects with the `markup` attribute as an empty string. ### Usage in development -In development environments, it can be easiest to set the `RENDER` setting to False. This ensures that the +In development environments, it can be easiest to set the `RENDER` setting to False. This ensures that the render server will not be used, hence you only need to manage your python process. Be aware that the render servers provided in the examples and elsewhere rely on Node.js's module system diff --git a/react/render.py b/react/render.py index 01b4aa8..83173d0 100644 --- a/react/render.py +++ b/react/render.py @@ -4,7 +4,7 @@ from .render_server import render_server -def render_component(path, props=None, to_static_markup=False, renderer=render_server): +def render_component(path, props=None, to_static_markup=False, renderer=render_server, request_headers=None): if not os.path.isabs(path): abs_path = staticfiles.find(path) if not abs_path: @@ -14,4 +14,4 @@ def render_component(path, props=None, to_static_markup=False, renderer=render_s if not os.path.exists(path): raise ComponentSourceFileNotFound(path) - return renderer.render(path, props, to_static_markup) \ No newline at end of file + return renderer.render(path, props, to_static_markup, request_headers) diff --git a/react/render_server.py b/react/render_server.py index b9f108a..63b07be 100644 --- a/react/render_server.py +++ b/react/render_server.py @@ -20,9 +20,9 @@ def __unicode__(self): class RenderServer(object): - def render(self, path, props=None, to_static_markup=False): + def render(self, path, props=None, to_static_markup=False, request_headers=None): url = conf.settings.RENDER_URL - + if props is not None: serialized_props = json.dumps(props, cls=JSONEncoder) else: @@ -39,11 +39,17 @@ def render(self, path, props=None, to_static_markup=False): serialized_options = json.dumps(options) options_hash = hashlib.sha1(serialized_options.encode('utf-8')).hexdigest() + all_request_headers = {'content-type': 'application/json'} + + # Add additional requests headers if the requet_headers dictionary is specified + if request_headers is not None: + all_request_headers.update(request_headers) + try: res = requests.post( url, data=serialized_options, - headers={'content-type': 'application/json'}, + headers=all_request_headers, params={'hash': options_hash} ) except requests.ConnectionError: diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 0e3ac71..c6ed898 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -2,10 +2,12 @@ from optional_django import six import mock from react.conf import Conf +from react import render_server from react.render import render_component from react.render_server import RenderedComponent from react.exceptions import ReactRenderingError, ComponentSourceFileNotFound from .settings import Components +import json class TestRendering(unittest.TestCase): @@ -102,4 +104,38 @@ def test_render_setting_is_respected(self): self.assertIsInstance(rendered, RenderedComponent) self.assertEqual(rendered.markup, '') self.assertEqual(str(rendered), '') - self.assertEqual(rendered.props, '{"name": "world!"}') \ No newline at end of file + self.assertEqual(rendered.props, '{"name": "world!"}') + + @mock.patch('requests.post') + def test_can_pass_additional_request_headers(self, requests_post_mock): + mock_json = { + 'markup': '