Skip to content

Commit cd1ffb4

Browse files
committed
Preflight requests now work with HAProxy 2.2 or with earlier versions, but in different ways.
1 parent 95e8c62 commit cd1ffb4

File tree

2 files changed

+58
-20
lines changed

2 files changed

+58
-20
lines changed

example/haproxy/cors.lua

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ function get_allowed_origin(origin, allowed_origins)
3232
allowed_origins[index] = value:gsub("%s+", "")
3333
end
3434

35-
core.Debug("CORS - Origin: " .. origin)
36-
3735
if contains(allowed_origins, "*") then
3836
return "*"
3937
elseif contains(allowed_origins, origin:match("//([^/]+)")) then
@@ -44,16 +42,29 @@ function get_allowed_origin(origin, allowed_origins)
4442
return nil
4543
end
4644

45+
-- Adds headers for CORS preflight request and then attaches them to the response
46+
-- after it comes back from the server. This works with versions of HAProxy prior to 2.2.
47+
-- The downside is that the OPTIONS request must be sent to the backend server first and can't
48+
-- be intercepted and returned immediately.
49+
-- txn: The current transaction object that gives access to response properties
50+
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
51+
function preflight_request_ver1(txn, allowed_methods)
52+
core.Debug("CORS: preflight request received")
53+
txn.http:res_add_header("Access-Control-Allow-Methods", allowed_methods)
54+
txn.http:res_add_header("Access-Control-Max-Age", 600)
55+
core.Debug("CORS: attaching allowed methods to response")
56+
end
57+
4758
-- Add headers for CORS preflight request and then returns a 204 response.
59+
-- The 'reply' function used here is available in HAProxy 2.2+. It allows HAProxy to return
60+
-- a reply without contacting the server.
4861
-- txn: The current transaction object that gives access to response properties
49-
-- method: The HTTP method
5062
-- origin: The value from the 'origin' request header
5163
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
5264
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
53-
function preflight_request(txn, method, origin, allowed_methods, allowed_origins)
54-
core.Debug("CORS: preflight request OPTIONS")
65+
function preflight_request_ver2(txn, origin, allowed_methods, allowed_origins)
66+
core.Debug("CORS: preflight request received")
5567

56-
-- NOTE: The 'reply' function is available in HAProxy 2.2+
5768
local reply = txn:reply()
5869
reply:set_status(204, "No Content")
5970
reply:add_header("Content-Type", "text/html")
@@ -69,12 +80,13 @@ function preflight_request(txn, method, origin, allowed_methods, allowed_origins
6980
reply:add_header("Access-Control-Allow-Origin", allowed_origin)
7081
end
7182

72-
core.Debug("CORS: Returning reply to CORS preflight request")
83+
core.Debug("CORS: Returning reply to preflight request")
7384
txn:done(reply)
7485
end
7586

7687
-- When invoked during a request, captures the origin header if present and stores it in a private variable.
77-
-- If the request is OPTIONS, returns a preflight request reply.
88+
-- If the request is OPTIONS and it is a supported version of HAProxy, returns a preflight request reply.
89+
-- Otherwise, the preflight request header is added to the response after it has returned from the server.
7890
-- txn: The current transaction object that gives access to response properties
7991
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
8092
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
@@ -95,9 +107,10 @@ function cors_request(txn, allowed_methods, allowed_origins)
95107
txn:set_priv(transaction_data)
96108

97109
local method = txn.sf:method()
110+
transaction_data["method"] = method
98111

99-
if method == "OPTIONS" then
100-
preflight_request(txn, method, origin, allowed_methods, allowed_origins)
112+
if method == "OPTIONS" and txn.reply ~= nil then
113+
preflight_request_ver2(txn, origin, allowed_methods, allowed_origins)
101114
end
102115
end
103116

@@ -107,6 +120,8 @@ function cors_response(txn)
107120
local transaction_data = txn:get_priv()
108121
local origin = transaction_data["origin"]
109122
local allowed_origins = transaction_data["allowed_origins"]
123+
local allowed_methods = transaction_data["allowed_methods"]
124+
local method = transaction_data["method"]
110125

111126
-- Always vary on the Origin
112127
txn.http:res_add_header("Vary", "Accept-Encoding,Origin")
@@ -121,6 +136,10 @@ function cors_response(txn)
121136
if allowed_origin == nil then
122137
core.Debug("CORS: " .. origin .. " not allowed")
123138
else
139+
if method == "OPTIONS" and txn.reply == nil then
140+
preflight_request_ver1(txn, allowed_methods)
141+
end
142+
124143
core.Debug("CORS: " .. origin .. " allowed")
125144
txn.http:res_add_header("Access-Control-Allow-Origin", allowed_origin)
126145
end

lib/cors.lua

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ function get_allowed_origin(origin, allowed_origins)
3232
allowed_origins[index] = value:gsub("%s+", "")
3333
end
3434

35-
core.Debug("CORS - Origin: " .. origin)
36-
3735
if contains(allowed_origins, "*") then
3836
return "*"
3937
elseif contains(allowed_origins, origin:match("//([^/]+)")) then
@@ -44,16 +42,29 @@ function get_allowed_origin(origin, allowed_origins)
4442
return nil
4543
end
4644

45+
-- Adds headers for CORS preflight request and then attaches them to the response
46+
-- after it comes back from the server. This works with versions of HAProxy prior to 2.2.
47+
-- The downside is that the OPTIONS request must be sent to the backend server first and can't
48+
-- be intercepted and returned immediately.
49+
-- txn: The current transaction object that gives access to response properties
50+
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
51+
function preflight_request_ver1(txn, allowed_methods)
52+
core.Debug("CORS: preflight request received")
53+
txn.http:res_add_header("Access-Control-Allow-Methods", allowed_methods)
54+
txn.http:res_add_header("Access-Control-Max-Age", 600)
55+
core.Debug("CORS: attaching allowed methods to response")
56+
end
57+
4758
-- Add headers for CORS preflight request and then returns a 204 response.
59+
-- The 'reply' function used here is available in HAProxy 2.2+. It allows HAProxy to return
60+
-- a reply without contacting the server.
4861
-- txn: The current transaction object that gives access to response properties
49-
-- method: The HTTP method
5062
-- origin: The value from the 'origin' request header
5163
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
5264
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
53-
function preflight_request(txn, method, origin, allowed_methods, allowed_origins)
54-
core.Debug("CORS: preflight request OPTIONS")
65+
function preflight_request_ver2(txn, origin, allowed_methods, allowed_origins)
66+
core.Debug("CORS: preflight request received")
5567

56-
-- NOTE: The 'reply' function is available in HAProxy 2.2+
5768
local reply = txn:reply()
5869
reply:set_status(204, "No Content")
5970
reply:add_header("Content-Type", "text/html")
@@ -69,12 +80,13 @@ function preflight_request(txn, method, origin, allowed_methods, allowed_origins
6980
reply:add_header("Access-Control-Allow-Origin", allowed_origin)
7081
end
7182

72-
core.Debug("CORS: Returning reply to CORS preflight request")
83+
core.Debug("CORS: Returning reply to preflight request")
7384
txn:done(reply)
7485
end
7586

7687
-- When invoked during a request, captures the origin header if present and stores it in a private variable.
77-
-- If the request is OPTIONS, returns a preflight request reply.
88+
-- If the request is OPTIONS and it is a supported version of HAProxy, returns a preflight request reply.
89+
-- Otherwise, the preflight request header is added to the response after it has returned from the server.
7890
-- txn: The current transaction object that gives access to response properties
7991
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
8092
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
@@ -95,9 +107,10 @@ function cors_request(txn, allowed_methods, allowed_origins)
95107
txn:set_priv(transaction_data)
96108

97109
local method = txn.sf:method()
110+
transaction_data["method"] = method
98111

99-
if method == "OPTIONS" then
100-
preflight_request(txn, method, origin, allowed_methods, allowed_origins)
112+
if method == "OPTIONS" and txn.reply ~= nil then
113+
preflight_request_ver2(txn, origin, allowed_methods, allowed_origins)
101114
end
102115
end
103116

@@ -107,6 +120,8 @@ function cors_response(txn)
107120
local transaction_data = txn:get_priv()
108121
local origin = transaction_data["origin"]
109122
local allowed_origins = transaction_data["allowed_origins"]
123+
local allowed_methods = transaction_data["allowed_methods"]
124+
local method = transaction_data["method"]
110125

111126
-- Always vary on the Origin
112127
txn.http:res_add_header("Vary", "Accept-Encoding,Origin")
@@ -121,6 +136,10 @@ function cors_response(txn)
121136
if allowed_origin == nil then
122137
core.Debug("CORS: " .. origin .. " not allowed")
123138
else
139+
if method == "OPTIONS" and txn.reply == nil then
140+
preflight_request_ver1(txn, allowed_methods)
141+
end
142+
124143
core.Debug("CORS: " .. origin .. " allowed")
125144
txn.http:res_add_header("Access-Control-Allow-Origin", allowed_origin)
126145
end

0 commit comments

Comments
 (0)