- Express Server (Proxy Server)
- Angular Client
- e31-assignment-08-proxy-and-client-wreilly
- LibraryThing.com
- >> Demo Online <<
- Usage: Install, Build, Deploy
- End User Instructions
- Technical Notes
Table of contents generated with markdown-toc
My two-part application is used to reach a LibraryThing.com (https://www.librarything.com) Web Service that is NOT configured to allow for "Cross-Origin Resource Sharing" (CORS)
Additionally, the Angular app integrates two parsers to handle XML as the response format.
- Assignment 8 (Graduate Credit Extra Work)
- CSCI-E31 https://canvas.harvard.edu/courses/35096
- wreilly2001@gmail.com
- April 30, 2018
- http://104.236.198.117:3000/ (c/o Digital Ocean)
- Obtain WS API Key from [librarything.com] (https://www.librarything.com/services/keys.php)
- Enter key into /proxy-server/.env
- SEE DEMO ONLINE to avoid need for key, etc. Thank you.
Instructions for developing and testing Proxy Server and Client on Local Machine
(See further below for instructions on deploying to Production (Digital Ocean).)
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd proxy-server
- $ npm install
- cd ../ng-client
- $ npm install
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd ng-client
- ng build (creates /dist; uses "local" environment)
- (npm note: I tried
npm run build-local(to run same command: ng build, but was buggy))
- (npm note: I tried
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd ng-client
- ng build --env=prod (creates /dist; uses "prod" environment)
- (npm note: I tried
npm run build(to run same command: ng build --env=prod, but was buggy))
- (npm note: I tried
Local Client App will run with either Client Build you create: Local or Production
A. For the Local Build, this Local Client App points to the Local Proxy Server on http://0.0.0.0:3000/
B. For the Production Build, here used in the LOCAL Client App, it will actually point up to the Production Digital Ocean Proxy Server http://103.236.198.117:3000/
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd proxy-server
- npm run start
Instructions for getting Proxy Server and Client onto Digital Ocean (D.O.)
- Log in to LOCAL
- Do "Client Build for Deploying to Digital Ocean" (see above)
- git commit -a -m 'Latest Client App Prod Build to /dist'
- git push
This gets code for both Proxy Server and Client App Build
- Log in to Digital Ocean
- First time: # git clone git@github.com:wreilly/e31-assignment-08-proxy-and-client-wreilly.git
- Subsequent updates: # git pull
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd proxy-server
- $ npm install
- (Note: Not necessary to do 'npm install' on the Angular client.)
- $ pwd -> /e31-assignment-08-proxy-and-client-wreilly
- cd proxy-server
- nohup npm run start &
- (optional): tail -f nohup.out
- http://104.236.198.117:3000/ (Digital Ocean)
See application homepage.
- Essentially:
- Simple input box entry for a LibraryThing book_id number (e.g. 1528).
- Retrieves the LibraryThing "Common Knowledge" record for that title (The Red Badge of Courage)
- Displays to screen a couple bibliographic fields
CORS: "Cross-Origin Resource Sharing"
Q. What problem is this solving?
A. To begin, a little narrative, about the browser's "Same Origin Policy":
Assume you are logged into Facebook and visit a malicious website in another browser tab.
Without the same origin policy JavaScript on that website could do anything
to your Facebook account that you are allowed to do.
For example read private messages, post status updates, or
analyse the HTML DOM-tree after you entered your password...
(https://teamgaslight.com/blog/circumventing-same-origin-policy-using-a-proxy-server)
That is, this Same Origin Policy means that:
- Browser visiting web app on one domain (e.g. http://localhost:4200) ...
- ... is not allowed to make service request from that app over to another domain (e.g. https://www.librarything.com) ...
- ... unless that other domain does set server headers to allow Cross-Origin Resource Sharing.
In our example app, we see that:
- FakeAPI.com does allow Cross-Origin Resource Sharing, and our app request direct to that API works fine.
- Response Headers:
- Access-Control-Allow-Origin: *
- Response Headers:
- LibraryThing.com on the other hand does not have CORS headers set. A direct app request from the browser will fail.
- Response Headers: (none of these are "Access-Control-Allow-Origin")
- Connection: keep-alive
- Content-Encoding: gzip
- Content-Type: application/xml; charset=UTF-8
- Date: Mon, 30 Apr 2018 09:36:48 GMT
- lt-backend: 192.168.0.102:80
- Server: nginx
- Transfer-Encoding: chunked
- X-Clacks-Overhead: GNU Terry Pratchett
- Response Headers: (none of these are "Access-Control-Allow-Origin")
Selected Resources:
- https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
- https://fetch.spec.whatwg.org/#http-access-control-allow-origin
To test, I did temporarily put a web service request to LibraryThing.com directly into my Angular client app. It caused this error:
Failed to load
http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&apikey=59211e...&id=1060:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://0.0.0.0:4200' is therefore not allowed access.
One solution (which we use in this app) is to create a Proxy Server which your app calls, to have it issue the web service request to LibraryThing.com.
There is no issue with "Cross-Origin" when the request is coming from a server, instead of from a browser.
Helpful explanatory article: https://teamgaslight.com/blog/circumventing-same-origin-policy-using-a-proxy-server
As a side note:
Finally, at the risk of confusion, if YOU are OWNER of a Server (say if you were owner of LibraryThing) and you wanted to make it "CORS-Enabled," here is the beginning of some information on the middleware you would use (for Node.js and Express anyway):
https://enable-cors.org/server_expressjs.html
https://fetch.spec.whatwg.org/#http-cors-protocol
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
Notes on Processing the XML (as String) Response:
-
1). LibraryThing Web Services API returns XML as String (to my Proxy Server).
-
2). Proxy Server returns XML String to Angular Client, which uses Browser's window.DOMParser to create XML Document from String.
- I used Inject 'window' as Angular Service, to access the DOMParser
-
3). Angular Client integrates with 3rd party JavaScript: 'parse.js' (which in turn has dependency on Lodash), to create JavaScript object from XML Document, parts of which finally are displayed to Client app webpage.
- parse.js:
- Convert to parse.ts.
- Export functions for import into Angular components
- Lodash:
- npm install lodash
- npm install @types/lodash
- import * as _ from 'lodash'
- parse.js: