{"id":19522204,"url":"https://github.com/captaincodeman/prpl-server-go","last_synced_at":"2025-04-26T09:32:07.058Z","repository":{"id":66357900,"uuid":"99177188","full_name":"CaptainCodeman/prpl-server-go","owner":"CaptainCodeman","description":"Go implementation of the PRPL pattern for serving Progressive Web Apps","archived":false,"fork":false,"pushed_at":"2019-10-11T14:03:47.000Z","size":33,"stargazers_count":31,"open_issues_count":7,"forks_count":7,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-04T10:34:06.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CaptainCodeman.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-03T01:34:29.000Z","updated_at":"2021-04-01T07:14:26.000Z","dependencies_parsed_at":"2023-02-22T12:30:55.922Z","dependency_job_id":null,"html_url":"https://github.com/CaptainCodeman/prpl-server-go","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fprpl-server-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fprpl-server-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fprpl-server-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fprpl-server-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CaptainCodeman","download_url":"https://codeload.github.com/CaptainCodeman/prpl-server-go/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250967238,"owners_count":21515564,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T00:37:45.066Z","updated_at":"2025-04-26T09:32:07.026Z","avatar_url":"https://github.com/CaptainCodeman.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# prpl-server-go\n\nAn HTTP server for Go designed to serve [PRPL](https://developers.google.com/web/fundamentals/performance/prpl-pattern/) apps in production.\n\nSee [live example here](https://prpl-dot-captain-codeman.appspot.com/)\n\nLighthouse score (remote / over the intertubes):\n\n![lighthouse performance](https://user-images.githubusercontent.com/304910/31322517-8c8f942c-ac56-11e7-96da-0c2a9cbb9a7c.png)\n\n## Usage\n\n### As a binary\n```sh\n$ go install github.com/captaincodeman/prpl-server-go\n$ prpl-server --root . --config ./polymer.json\n```\n\n### As a library\n\n```sh\n$ go get github.com/captaincodeman/prpl-server-go\n```\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\t\"github.com/captaincodeman/prpl-server-go\"\n)\n\nfunc main() {\n\tm, _ := prpl.New(\n        prpl.WithRoot(\"build\"),\n        prpl.WithConfigFile(\"build/polymer.json\"),\n    )\n\n\thttp.ListenAndServe(\":8080\", m)\n}\n```\n\n## Differential Serving\n\nModern browsers offer great features that improve performance, but most applications need to support older browsers too. prpl-server can serve different versions of your application to different browsers by detecting browser capabilities using the user-agent header.\n\n### Builds\n\nprpl-server understands the notion of a *build*, a variant of your application optimized for a particular set of browser capabilities.\n\nBuilds are specified in a JSON configuration file. This format is compatible with [`polymer.json`](https://www.polymer-project.org/2.0/docs/tools/polymer-json), so if you are already using polymer-cli for your build pipeline, you can annotate your existing builds with browser capabilities, and copy the configuration to your server root. prpl-server will look for a file called `polymer.json` in the server root, or you can specify it directly with the `--config` flag.\n\n\nIn this example we define two builds, one for modern browsers that support ES2015 and HTTP/2 Push, and a fallback build for other browsers:\n\n```\n{\n  \"entrypoint: \"index.html\",\n  \"builds\": [\n    {\"name\": \"modern\", \"browserCapabilities\": [\"es2015\", \"push\"]},\n    {\"name\": \"fallback\"}\n  ]\n}\n```\n\n### Capabilities\n\nThe `browserCapabilities` field defines the browser features required for that build. prpl-server analyzes the request user-agent header and picks the best build for which all capabilities are met. If multiple builds are compatible, the one with more capabilities is preferred. If there is a tie, the build that comes earlier in the configuration file wins.\n\nYou should always include a fallback build with no capability requirements. If you don't, prpl-server will warn at startup, and will return a 500 error on entrypoint requests to browsers for which no build can be served.\n\nThe following keywords are supported. See also [capabilities.ts](https://github.com/Polymer/prpl-server-node/blob/master/src/capabilities.ts) for the latest browser support matrix.\n\n| Keyword       | Description\n| :----         | :----\n| es2015        | [ECMAScript 2015 (aka ES6)](https://developers.google.com/web/shows/ttt/series-2/es2015)\n| push          | [HTTP/2 Server Push](https://developers.google.com/web/fundamentals/performance/http2/#server-push)\n| serviceworker | [Service Worker API](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers)\n\n\n## Entrypoint\n\nIn the [PRPL pattern](https://developers.google.com/web/fundamentals/performance/prpl-pattern/), the *entrypoint* is a small HTML file that acts as the application bootstrap.\n\nprpl-server will serve the entrypoint from the best compatible build from `/`, and from any path that does not have a file extension and is not an existing file.\n\nprpl-server expects that each build subdirectory contains its own entrypoint file. By default it is `index.html`, or you can specify another name with the `entrypoint` configuration file setting.\n\nNote that because the entrypoint is served from many URLs, and varies by user-agent, cache hits for the entrypoint will be minimal, so it should be kept as small as possible.\n\n## Base paths\n\nSince prpl-server serves resources from build subdirectories, your application source can't know the absolute URLs of build-specific resources upfront.\n\nFor most documents in your application, the solution is to use relative URLs to refer to other resources in the build, and absolute URLs to refer to resources outside of the build (e.g. static assets, APIs). However, since the *entrypoint* is served from URLs that do not match its location in the build tree, relative URLs will not resolve correctly.\n\nThe solution we recommend is to place a [`\u003cbase\u003e`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag in your entrypoint to anchor its relative URLs to the correct build subdirectory, regardless of the URL the entrypoint was served from. You may then use relative URLs to refer to build-specific resources from your entrypoint, as though you were in your build subdirectory. Put `\u003cbase href=\"/\"\u003e` in your source entrypoint, so that URLs resolve when serving your source directly during development. In your build pipeline, update each entrypoint's base tag to match its build subdirectory (e.g. `\u003cbase href=\"/modern/\"\u003e`).\n\nIf you are using polymer-cli, set `{basePath: true}` on each build configuration to perform this base tag update automatically.\n\nNote that `\u003cbase\u003e` tags only affect relative URLs, so to refer to resources outside of the build from your entrypoint, use absolute URLs as you normally would.\n\n## HTTP/2 Server Push\n\nServer Push allows an HTTP/2 server to preemptively send additional resources alongside a response. This can improve latency by eliminating subsequent round-trips for dependencies such as scripts, CSS, and HTML imports.\n\n\n### Push manifest\n\nprpl-server looks for a file called `push-manifest.json` in each build subdirectory, and uses it to map incoming request paths to the additional resources that should be pushed with it. The push manifest file format is described [here](https://github.com/GoogleChrome/http2-push-manifest). Tools for generating a push manifest include [http2-push-manifest](https://github.com/GoogleChrome/http2-push-manifest) and [polymer-cli](https://github.com/Polymer/polymer-cli).\n\nResources in the push manifest can be specified as absolute or relative paths. Absolute paths are interpreted relative to the server root directory. Relative paths are interpreted relative to the location of the push manifest file itself (i.e. the build subdirectory), so that they do not need to know which build subdirectory they are being served from. Push manifests generated by `polymer-cli` always use relative paths.\n\n### Link preload headers\n\nprpl-server is designed to be used behind an HTTP/2 reverse proxy, and currently does not generate push responses itself. Instead it sets [preload link](https://w3c.github.io/preload/#server-push-http-2) headers, which are intercepted by cooperating reverse proxy servers and upgraded into push responses. Servers that implement this upgrading behavior include [Apache](https://httpd.apache.org/docs/trunk/mod/mod_http2.html#h2push), [nghttpx](https://github.com/nghttp2/nghttp2#nghttpx---proxy), and [Google App Engine](https://cloud.google.com/appengine/).\n\n### Testing push locally\n\nTo confirm your push manifest is working during local development, you can look for `Link: \u003cURL\u003e; rel=preload` response headers in your browser dev tools.\n\nTo see genuine push locally, you will need to run a local HTTP/2 reverse proxy such as [nghttpx](https://github.com/nghttp2/nghttp2#nghttpx---proxy):\n\n- Install nghttpx ([Homebrew](http://brewformulas.org/Nghttp2), [Ubuntu](http://packages.ubuntu.com/zesty/nghttp2), [source](https://github.com/nghttp2/nghttp2#building-from-git)).\n- Generate a self-signed TLS certificate, e.g. `openssl req -newkey rsa:2048 -x509 -nodes -keyout server.key -out server.crt`\n- Start prpl-server (assuming default `127.0.0.1:8080`).\n- Start nghttpx: `nghttpx -f127.0.0.1,8443 -b127.0.0.1,8080 server.key server.crt --no-ocsp`\n- Visit `https://localhost:8443`. In Chrome, Push responses will show up in the Network tab as Initiator: Push / Other.\n\nNote that Chrome will not allow a service worker to be registered over HTTPS with a self-signed certificate. You can enable [chrome://flags/#allow-insecure-localhost](chrome://flags/#allow-insecure-localhost) to bypass this check. See [this page](https://www.chromium.org/blink/serviceworker/service-worker-faq) for more tips on developing service workers in Chrome.\n\n## Service Workers\n\nprpl-server sets the [`Service-Worker-Allowed`](https://www.w3.org/TR/service-workers-1/#service-worker-allowed) header to `/` for any request path ending with `service-worker.js`. This allows a service worker served from a build subdirectory to be registered with a scope outside of that directory, e.g. `register('service-worker.js', {scope: '/'})`.\n\n## HTTPS\n\nYour apps should always be served over HTTPS. It protects your user's data, and is *required* for features like service workers and HTTP/2.\n\nIf the `--https-redirect` flag is set, prpl-server will redirect all HTTP requests to HTTPS. It sends a `301 Moved Permanently` redirect to an `https://` address with the same hostname on the default HTTPS port (443).\n\nprpl-server trusts [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) and [`X-Forwarded-Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) headers from your reverse proxy to determine the client's true protocol and hostname. Most reverse proxies automatically set these headers, but if you encounter issues with redirect loops, missing or incorrect `X-Forwarded-*` headers may be the cause.\n\nYou should always use `--https-redirect` in production, unless your reverse proxy already performs HTTPS redirection.\n\n## Google App Engine Quickstart\n\n[Google App Engine](https://cloud.google.com/appengine/) is a managed server platform that [supports Go](https://cloud.google.com/appengine/docs/go/) in its [Standard Environment](https://cloud.google.com/appengine/docs/standard/). You can deploy prpl-server to App Engine with a few steps:\n\n1. Follow [these instructions](https://cloud.google.com/appengine/docs/standard/go/quickstart) to set up a Google Cloud project and install the Google Cloud SDK. As instructed, run the `gcloud init` command to authenticate and choose your project ID.\n\n2. `cd` to the directory you want to serve (e.g. a `server/` directory withint your polymer-cli project).\n\n3. Create a symlink to include the contents of the build output folder:\n\n    ln -s ../build static\n\n4. Create an `app.go` file. This is the command App Engine runs when your app starts and contains details of the version of your app, where the files are located and how routes map to fragments.\n\n```go\npackage app\n\nimport (\n\t\"os\"\n\n\t\"net/http\"\n\n\t\"github.com/captaincodeman/prpl-server-go\"\n)\n\nfunc init() {\n\tversion := os.Getenv(\"STATIC_VERSION\")\n\tm, _ := prpl.New(\n\t\tprpl.WithVersion(version),\n\t\tprpl.WithRoot(\"./static\"),\n\t\tprpl.WithConfigFile(\"./static/polymer.json\"),\n\t\tprpl.WithRoutes(prpl.Routes{\n\t\t\t\"/\":      \"src/my-view1.html\",\n\t\t\t\"/view1\": \"src/my-view1.html\",\n\t\t\t\"/view2\": \"src/my-view2.html\",\n\t\t\t\"/view3\": \"src/my-view3.html\",\n\t\t}),\n\t)\n\n\thttp.Handle(\"/\", m)\n}\n```\n\n5. Create an `app.yaml` file. We have included a command-line tool to help automate this:\n\n    prpl-config --root static \n\t              --config polymer.json \n\t\t\t\t        --static-version 20170806 \u003e app.yaml\n\nNote the `static-version` parameter should be the same as the one included in the go file. This may be automated in future. Here's an example of the `app.yaml` file produced:\n\n```yaml\nservice: default\nruntime: go\napi_version: go1.8\n\ninstance_class: F1\n\nhandlers:\n- url: /20170806/es5-bundled/index.html\n  script: _go_app\n  secure: always\n\n- url: /20170806/es5-bundled/service-worker.js\n  static_files: static/es5-bundled/service-worker.js\n  upload: static/es5-bundled/service-worker.js\n  secure: always\n  http_headers:\n    Cache-Control: \"private, max-age=0, must-revalidate\"\n    Service-Worker-Allowed: \"/\"\n\n- url: /20170806/es5-bundled/\n  static_dir: static/es5-bundled/\n  application_readable: true\n  secure: always\n  http_headers:\n    Cache-Control: \"public, max-age=31536000, immutable\"\n\n- url: /20170806/es6-bundled/index.html\n  script: _go_app\n  secure: always\n\n- url: /20170806/es6-bundled/service-worker.js\n  static_files: static/es6-bundled/service-worker.js\n  upload: static/es6-bundled/service-worker.js\n  secure: always\n  http_headers:\n    Cache-Control: \"private, max-age=0, must-revalidate\"\n    Service-Worker-Allowed: \"/\"\n\n- url: /20170806/es6-bundled/\n  static_dir: static/es6-bundled/\n  application_readable: true\n  secure: always\n  http_headers:\n    Cache-Control: \"public, max-age=31536000, immutable\"\n\n- url: /20170806/es6-unbundled/index.html\n  script: _go_app\n  secure: always\n\n- url: /20170806/es6-unbundled/service-worker.js\n  static_files: static/es6-unbundled/service-worker.js\n  upload: static/es6-unbundled/service-worker.js\n  secure: always\n  http_headers:\n    Cache-Control: \"private, max-age=0, must-revalidate\"\n    Service-Worker-Allowed: \"/\"\n\n- url: /20170806/es6-unbundled/\n  static_dir: static/es6-unbundled/\n  application_readable: true\n  secure: always\n  http_headers:\n    Cache-Control: \"public, max-age=31536000, immutable\"\n\n- url: /.*\n  script: _go_app\n  secure: always\n\nenv_variables:\n  STATIC_VERSION: 20170806\n```\n\nThe configuration may seem complex but this is necessary to make optimal use of AppEngine's static file service and edge caching which avoids consuming instance CPU to serve most of the files while still allowing certain files to be modified (to add the version to the path which enables long cache espirations to be used).\n\nThere are really three handler mappings repeated for each build configuration: \n\n`url: /version/build/index.html` maps the entrypoint for each build to the app. The app will update the `\u003cbase href=\"/build/\"\u003e` tag to include the version string.\n\n`url: /version/build/service-worker.js` configures specific http headers for the service worker disable caching and to allow it to be used from a sub-folder.\n\n`url: /version/build/` configures the remaining static files for a build to be served by the edge cache with long expiration times.\n\nThe final handler mapping `url: /.*` ensures that regular app routes can be handled by the app. These check the browser's capabilities and direct it to the appropriate build variation by outputting the appropriate build's entrypoint, transformed to add the version string to the paths.\n\n6. Run `gcloud app deploy` to deploy to your App Engine project. `gcloud` will tell you the URL your app is being served from. For next steps, check out the Go on Google App Engine [documentation](https://cloud.google.com/appengine/docs/go/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fprpl-server-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaptaincodeman%2Fprpl-server-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fprpl-server-go/lists"}