Skip to content

Commit 2b6c977

Browse files
committed
Support rewriting the request_url in the HttpClient when no Host header is provided
1 parent 5a5975c commit 2b6c977

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

lib/shopify_api/clients/http_client.rb

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,18 @@ def request(request, response_as_struct: false)
3939
headers = @headers
4040
headers["Content-Type"] = T.must(request.body_type) if request.body_type
4141
headers = headers.merge(T.must(request.extra_headers)) if request.extra_headers
42-
if headers["Host"].include?(".my.shop.dev")
43-
headers["x-forwarded-host"] = headers["Host"]
44-
headers["Host"] = "app.shop.dev"
45-
end
42+
43+
parsed_uri = URI(request_url(request))
44+
45+
headers = append_first_party_development_headers(headers, parsed_uri)
4646

4747
tries = 0
4848
response = HttpResponse.new(code: 0, headers: {}, body: "")
4949
while tries < request.tries
5050
tries += 1
5151
res = T.cast(HTTParty.send(
5252
request.http_method,
53-
request_url(request),
53+
parsed_uri.to_s,
5454
headers: headers,
5555
query: request.query,
5656
body: request.body.class == Hash ? T.unsafe(request.body).to_json : request.body,
@@ -119,6 +119,25 @@ def serialized_error(response)
119119
end
120120
body.to_json
121121
end
122+
123+
private
124+
125+
sig do
126+
params(headers: T::Hash[T.any(Symbol, String), T.untyped],
127+
parsed_uri: URI::Generic).returns(T::Hash[T.any(Symbol, String), T.untyped])
128+
end
129+
def append_first_party_development_headers(headers, parsed_uri)
130+
return headers unless defined?(DevServer)
131+
return headers unless headers["Host"]&.include?(".my.shop.dev") || parsed_uri.host&.include?(".my.shop.dev")
132+
133+
# These headers are only used for first party applications in development mode
134+
headers["x-forwarded-host"] = headers["Host"] || parsed_uri.host
135+
headers["Host"] = T.unsafe(
136+
Object.const_get("DevServer::Core"), # rubocop:disable Sorbet/ConstantsFromStrings
137+
).new.host!(:app)
138+
139+
headers
140+
end
122141
end
123142
end
124143
end

test/clients/http_client_test.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,66 @@ def test_response_as_struct
289289
assert_equal("nested_value", response.body.key.nested_key)
290290
end
291291

292+
def test_rewrites_my_shop_dev_host_headers_when_present
293+
mock_dev_server_core_const
294+
295+
@success_body = {}
296+
shop = "test-shop.my.shop.dev"
297+
session = ShopifyAPI::Auth::Session.new(shop:, access_token: @token)
298+
299+
expected_headers = @expected_headers.dup
300+
expected_headers["Host"] = "app.shop.dev"
301+
expected_headers["x-forwarded-host"] = shop
302+
303+
stub_request(@request.http_method, "https://#{shop}#{@base_path}/#{@request.path}")
304+
.with(body: @request.body.to_json, query: @request.query, headers: expected_headers)
305+
.to_return(body: "", headers: @response_headers, status: 204)
306+
307+
@client = ShopifyAPI::Clients::HttpClient.new(session:, base_path: @base_path)
308+
@request = ShopifyAPI::Clients::HttpRequest.new(
309+
http_method: :post,
310+
path: "some-path",
311+
body: { foo: "bar" },
312+
body_type: "application/json",
313+
query: { id: 1234 },
314+
extra_headers: { extra: "header", "Host" => shop },
315+
)
316+
317+
verify_http_request
318+
ensure
319+
unmock_dev_server_core_const
320+
end
321+
322+
def test_appends_host_headers_for_my_shop_dev_request_url
323+
mock_dev_server_core_const
324+
325+
@success_body = {}
326+
shop = "test-shop.my.shop.dev"
327+
session = ShopifyAPI::Auth::Session.new(shop:, access_token: @token)
328+
329+
expected_headers = @expected_headers.dup
330+
expected_headers["Host"] = "app.shop.dev"
331+
expected_headers["x-forwarded-host"] = shop
332+
333+
stub_request(@request.http_method, "https://#{shop}#{@base_path}/#{@request.path}")
334+
.with(body: @request.body.to_json, query: @request.query, headers: expected_headers)
335+
.to_return(body: "", headers: @response_headers, status: 204)
336+
337+
@client = ShopifyAPI::Clients::HttpClient.new(session:, base_path: @base_path)
338+
@request = ShopifyAPI::Clients::HttpRequest.new(
339+
http_method: :post,
340+
path: "some-path",
341+
body: { foo: "bar" },
342+
body_type: "application/json",
343+
query: { id: 1234 },
344+
extra_headers: { extra: "header" },
345+
)
346+
347+
verify_http_request
348+
ensure
349+
unmock_dev_server_core_const
350+
end
351+
292352
private
293353

294354
def simple_http_test(http_method)
@@ -303,6 +363,19 @@ def apply_simple_http_stub
303363
.to_return(body: @success_body.to_json, headers: @response_headers)
304364
end
305365

366+
def mock_dev_server_core_const
367+
dev_server = Class.new
368+
core = Class.new
369+
core.stubs(:new).returns(mock(host!: "app.shop.dev"))
370+
371+
Object.const_set("DevServer", dev_server)
372+
dev_server.const_set("Core", core)
373+
end
374+
375+
def unmock_dev_server_core_const
376+
Object.const_set("DevServer", nil)
377+
end
378+
306379
def verify_http_request
307380
response = @client.request(@request)
308381

0 commit comments

Comments
 (0)