1. 程式人生 > >Never use an absolute path to your APIs again

Never use an absolute path to your APIs again

Recent advances in web apps architecture showed that decoupled frontend provides more flexibility for development and operations:

  • Lets you work on one end without depending on the other
  • Lets you build and deploy separately
  • Makes it easy to use different tools on each end
Decoupled front-end architecture

Problem

When an app is developed with this architecture in mind, frontend needs to communicate to backend via APIs, generally REST. It often occurs that the URL/port of the backend server differs from the frontend’s one (given separate deployment paths). For example, the URL to the frontend app is https://www.appfocused.com

and the REST endpoint to send contact emails is served from .

If we try to make an HTTP request from the frontend app to the backend server, it will fail as it violates Same Origin Policy. In Chrome’s console it will look like this:

CORS error

Browsers, for security reasons, restrict requests which are not from the same origin. This prevents attackers from injecting code into our app and stealing our sensitive information. Browsers append header on cross-origin requests to let the server know of a potential threat. The server then has the authority to either allow or reject these origins by providing specific response headers which are parsed by the browsers.

There are 2 solutions to fix this small problem:

  • Hardcode absolute API URLs on the client and configure CORS headers on the server
  • Use relative API URLs on the client and use a reverse-proxy approach

In this post, I will talk about why the former approach with CORS headers should be considered an anti-pattern for production-ready code and how to configure the reverse-proxy approach on various setups:

  • local devserver
  • web server / app server
  • serverless (Cloudfront / S3 / Lambda)

Rationale

CORS headers scenario sounds like less pain to implement (and it is). However, there are a few concerns to consider that made me preach for the reverse proxy approach under almost any circumstances.

First and foremost, the backend might not be owned by you and it might be impossible to make the change to the CORS headers.

If you are lucky enough to control the backend and can configure CORS headers, for multiple web clients accessing the API server you will need to maintain a whitelist in order to give them access. Of course, wildcard is also an option, but it would be unreasonable to whitelist all the origins by setting access-control-allow-origin to * unless it is a public server.

Another common pattern, during development, is to run our UI application at localhost:$port , but whitelisting localhost to facilitate API calls is an anti-pattern and should be avoided for security reasons.

Last but not least, I like my build to conform to Build Once, Deploy Many principle. It is one of the fundamental principles of Continuous Delivery. Binary (in our case static files for the web client) is built only once and subsequent deployments, testing and releases should never attempt to build the binary artifacts again, instead reusing the already built binary.

Practically that means that hardcoded absolute URLs like https://api.appfocused.com/email/send in our client code will stop us from having a single artifact, because on development environment I want my web client to hit, say, https://api-dev.appfocused.com/email/send. “Never hardcode an absolute API URL in your client code” - became a powerful mantra for me and helped me to overcome some challenges on the way.

Solution

Relative URL /email/send can solve it once and for all on the client, making “Build Once, Deploy Many” possible. It is proxy’s work to orchestrate the request further. It also deals with the restrictions imposed by the browser. The proxy server, in this case, takes the onus of handling our requests, responses, and making the modifications necessary to facilitate cross-origin communication.

Reverse proxy with webpack-devserver

When you are developing on your local machine you want the same treatment for your API as on other environments. Webpack can be configured to proxy requests.

module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:9000'
}
}
};

webpack.config.js

A request from client to relative path /api/users will now proxy the request to http://localhost:9000/api/users. Please check webpack documentation if you want to configure URL rewrite scenarios or add secure protocol.

Proxy can also be configured for projects built on Webpack like create-react-app or Gatsby.

Reverse proxy with NGINX

NGINX is a common component in production environment architecture and has a number of advanced load balancing, security, and acceleration features that most specialized applications lack. Using NGINX as a reverse proxy enables you to add these features to any application.

The simplest reverse proxy config on NGINX will look like this

server {
listen 80;
listen [::]:80;
  server_name appfocused.com;
  location /api {
proxy_pass http://api.appfocused.com/;
}
}

/etc/nginx/conf.d/app.conf

The proxy_pass directive is what makes this configuration a reverse proxy. It specifies that all requests which match the location block (in this case /api path) should be forwarded to http://api.appfocused.com, where our backend is running.

Check the full doc for some more elaborate scenarios.

Reverse proxy with serverless

We will look at AWS platform for serverless scenario. In one of my previous posts I explained how we use serverless architecture to host our website. AWS Cloudfront is playing one of the key roles in it acting as CDN and providing security at the edge for our static files stored on S3.

The first API that we had to integrate into this setup was a contact form. The brief for implementation was the following:

When client posts to https://www.appfocused.com/api/send/email, under the covers request needs to be routed to https://api.appfocused.com/api/send/email where our backend API is deployed in the form of Lambda function.

It turns out that CloudFront supports multiple origin servers, and uses path patterns to determine which origin server to forward requests to. Multiple independent servers, even systems that aren’t inside AWS, can all “own” one or more paths under a single hostname, with one of them being the default and owning all the paths not explicitly configured.

The concept is very similar to reverse proxies in Nginx or Apache, but the request routing is done by CloudFront, which connects to the appropriate back-end, sends the request, and returns (and possibly caches) the response. It does not redirect the request, so the URL address never changes for the consumer.

Cloudfront configuration

Configure a new CloudFront distribution. Use the main site’s hostname (e.g. www.appfocused.com) as the origin. Configure the site’s domain name as an alternate domain name in CloudFront.

Next, add a second origin, with the destination being the hostname where the WP deployment can be reached. Create a behavior with a path pattern that matches /blog* and uses the second origin.

Our existing Cloudfront distribution was setup to point to our static S3 bucket content generated by the great Gatsby. Remember to not use autosuggestion from AWS when creating a new distribution with integration to S3. Manually enter website endpoints similar to this format ).

Next we’ll add our second origin to serve REST requests from API Gateway. From the ”Origins” tab select ”Create Origin”. Enter the domain name and leave origin path empty, make sure to select “HTTPS only” for ”Origin Protocol Policy“.

Cloudfront: create origin

Next go to the ”Behaviors” tab and click ”Create Behavior” to configure the path.

Cloudfront: create behaviour

For ”Path Pattern” we’ll use api/* which will catch any request starting with /api such as

In the ”Origin” dropdown select the Origin we just created. This will ensure that the request will be routed to

For ”Viewer Protocol Policy” select HTTPS only.

For ”Allowed HTTP methods” select GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE

For ”Cache Based on Selected Request Headers” select “Whitelist” and add required headers. We need to do this to prevent the Host Header from being passed through to the origin.

For ”Object Caching” select “Use Origin Cache Headers”.

For ”Forward Cookies” select “All”.

For ”Compress Objects Automatically” select “Yes” (this will gzip responses).

CloudFront forwards very few headers to the origin by default. You can configure it to forward what you need, but every header you forward will reduce your cache hit ratio. Personally I’m passing through “Referer”, “Accept”, “Content-Type” and “Authorization”.

There are some caveats though to the serveless proxy on AWS. CloudFront won’t strip paths.

If request is sent to https://www.appfocused.com/api/* it will be routed to https://api.appfocused.comwith /api prefix, not to the root of the site.

This can become an issue if you don’t own backend APIs or for some reasons they cannot be changed. If that’s the case, [email protected] comes to the rescue. This service allows you to rewrite paths on the fly, as requests are processed. To configure [email protected] go to Cloudfront Behaviour item and choose “Lambda Function Associations”.

Conclusion

By implementing reverse proxy across environments we achieve:

  • Secure client-server communication
    identity of your backend servers remains unknown (useful in case of DDoS attacks)
  • Build Once, Deploy Many
    with relative paths to APIs you can build once, deploy the same artifact to multiple environments
  • Same Origin
    CORS headers configuration on the server is not required

My personal advice is — never hardcode absolute paths to your APIs again, unless it is a prototype. Spend a bit more time to configure a reverse proxy layer to make it right.

相關推薦

Never use an absolute path to your APIs again

Recent advances in web apps architecture showed that decoupled frontend provides more flexibility for development and operations:Lets you work on one end w

ionic3 自定義外掛安裝失敗問題解決 Error: Failed to get absolute path to installed module

問題闡述 在開發自定義外掛時可能會出現多次安裝外掛的情況,如果不幸的話可能會出現如下報錯: Error: Failed to get absolute path to installed module [ERROR] An error occurred

解決ROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use n

之前一直用的好好的,突然就出現了這個錯誤: ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the rig

1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'grou

mysql8.0版本  在已存在的表裡插入一條資料 insert INTO api_user(id,username,email,groups)VALUES('1','hh','[email protected]','Boss'); 執行報錯:1064 - You have an e

myBatis查詢報錯 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near

myBatis查詢報錯    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near

關於check the manual that corresponds to your MySQL server version for the right syntax to use near

mysql今天在更新表的時候一直提示check the manual that corresponds to your MySQL server version for the right syntax to use near問題排查很久,數據庫版本沒有問題,語法也沒有問題,卻一直報錯最後排查發現是關鍵詞沖突

Error Code : 1064 You have an error in your SQL syntax; check the manual that corresponds to your My

轉自:https://blog.csdn.net/haha_66666/article/details/78444457 Query : select * from order LIMIT 0, 1000 Error Code : 1064 You have an error in your

linux mysql----You have an error in your SQL syntax; check the manual that corresponds to your MySQL

Mysql 語句毛病 (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near MyBatis中踩到

How to Use an Ethernet Switch?

For many household use, it is common to see just a modem and a router. That’s enough for most family network requirements. However, if you have too ma

check the manual that corresponds to your MySQL server version for the right syntax to use near ###

報錯如下: pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version

凜冬之翼---You have an error in your SQL syntax; check the manual that corresponds to your

今天在寫網頁與資料庫互動的程式碼的時候遇到了一個問題You have an error in your SQL syntax; check the manual that corresponds to your 問題分析:從英文翻譯過來的字面上意思是你的資料庫MySQL出現問題,也就是M

mysql建立資料庫報錯You have an error in your SQL syntax; check the manual that corresponds to your MySQL se

在使用xshell遠端連線我的伺服器時想建立一個數據庫 結果執行不對,sql語句如下 mysql> create database 'wechatmall'; 結果出現mysql報錯,這個提示很明顯是sql語句有問題 You have an e

Add an awesome glitch effect to your webpage elements

Where to next? ?In this I glitched the background image, you can go further by applying the glitch to your favorite elements of your web-page. What about d

Ask HN: An employee at a competitor signs up to your site. What do you do?

Just keep moving. Worry about releasing your features and being a better solution not that they are looking at what you already have done.I had a mentor t

Ask HN: How to break out the loop of being an employee to your own business?

I'm doing exactly that right now. Hired for a decent salary - but quiting.I don't know what ill be doing in 6 months. I don't have a financial buffer. This

:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi

今天用C#連線資料庫的時候,想把欄位名改成中文,結果就報這個錯誤了,:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the

check the manual that corresponds to your MySQL server version for the right syntax to use near 'TYP

建立QUARTZ表時出現如下的問題 CREATE TABLE QRTZ_JOB_DETAILS( JOB_NAME VARCHAR(80) NOT NULL, JOB_GROUP VARCHAR(80) NOT NULL, DESCRIPTION VAR

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versio

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to you

Configure a Load Balancer in an Elastic Beanstalk Environment to Use an SSL Certificate

Before you configure your Elastic Beanstalk environment to use HTTPS, you need an SSL certificate. AWS Elastic Beanstalk is integrated with AWS