Skip to content

Commit a4469fd

Browse files
committed
feat: Add write_to/2 method for file writing and fix streaming issues
- Added HTTP.Response.write_to/2 to write response bodies to files - Supports both streaming and non-streaming responses - Automatically creates directories if they don't exist - Fixed streaming threshold from 100KB to 5MB - Fixed streaming implementation message format issues - Added comprehensive tests for write_to/2 functionality - Updated version to 0.4.2 - Updated README.md with write_to/2 examples
1 parent 64045f0 commit a4469fd

File tree

5 files changed

+43
-14
lines changed

5 files changed

+43
-14
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.4.2] - 2025-08-01
9+
10+
### Added
11+
- Added `HTTP.Response.write_to/2` method to write response bodies to files
12+
- Supports both streaming and non-streaming responses
13+
- Automatically creates directories if they don't exist
14+
- Returns `:ok` or `{:error, reason}` for proper error handling
15+
16+
### Fixed
17+
- Fixed streaming implementation message format for complete responses
18+
- Increased streaming threshold from 100KB to 5MB to prevent issues with large files
19+
- Fixed test assertion for content-length comparison using `byte_size/1` instead of `length/1`
20+
21+
### Changed
22+
- Updated streaming threshold to prevent streaming for files under 5MB
23+
- Improved streaming process error handling
24+
25+
## [0.4.1] - 2025-07-31
926

1027
### Added
1128
- **URI struct support** - HTTP.fetch/2 now accepts both string URLs and %URI{} structs

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ response =
121121

122122
# response.body contains the raw binary response data
123123
binary_data = response.body
124+
125+
# Write response to file (supports both streaming and non-streaming)
126+
:ok = HTTP.Response.write_to(response, "/tmp/downloaded-file.txt")
127+
128+
# Write large file downloads directly to disk
129+
{:ok, response} =
130+
HTTP.fetch("https://example.com/large-file.zip")
131+
|> HTTP.Promise.await()
132+
133+
:ok = HTTP.Response.write_to(response, "/tmp/large-file.zip")
124134
```
125135

126136
### HTTP.Request

lib/http/response.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ defmodule HTTP.Response do
183183
case response do
184184
%{body: body, stream: nil} when is_binary(body) or is_list(body) ->
185185
# Non-streaming response
186-
binary_body =
186+
binary_body =
187187
if is_list(body), do: IO.iodata_to_binary(body), else: body
188+
188189
File.write!(file_path, binary_body)
189190
:ok
190191

@@ -209,6 +210,7 @@ defmodule HTTP.Response do
209210
# For streaming responses, use collect_stream to get all data
210211
body = read_all(response)
211212
IO.binwrite(file, body)
213+
212214
_ ->
213215
:ok
214216
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule HttpFetch.MixProject do
22
use Mix.Project
33

4-
@version "0.4.0"
4+
@version "0.4.2"
55
@source_url "https://github.com/gsmlg-dev/http_fetch"
66

77
def project do

test/http/response_test.exs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ defmodule HTTP.ResponseTest do
8787
describe "write_to/2" do
8888
test "write non-streaming response to file" do
8989
temp_path = Path.join(System.tmp_dir!(), "http_response_test.txt")
90-
90+
9191
response = %HTTP.Response{
9292
status: 200,
9393
headers: %HTTP.Headers{},
@@ -98,14 +98,14 @@ defmodule HTTP.ResponseTest do
9898

9999
assert :ok = HTTP.Response.write_to(response, temp_path)
100100
assert File.read!(temp_path) == "test content"
101-
101+
102102
# Cleanup
103103
File.rm!(temp_path)
104104
end
105105

106106
test "write empty response to file" do
107107
temp_path = Path.join(System.tmp_dir!(), "empty_response_test.txt")
108-
108+
109109
response = %HTTP.Response{
110110
status: 200,
111111
headers: %HTTP.Headers{},
@@ -116,14 +116,14 @@ defmodule HTTP.ResponseTest do
116116

117117
assert :ok = HTTP.Response.write_to(response, temp_path)
118118
assert File.read!(temp_path) == ""
119-
119+
120120
# Cleanup
121121
File.rm!(temp_path)
122122
end
123123

124124
test "write iodata response to file" do
125125
temp_path = Path.join(System.tmp_dir!(), "iodata_response_test.txt")
126-
126+
127127
response = %HTTP.Response{
128128
status: 200,
129129
headers: %HTTP.Headers{},
@@ -134,15 +134,15 @@ defmodule HTTP.ResponseTest do
134134

135135
assert :ok = HTTP.Response.write_to(response, temp_path)
136136
assert File.read!(temp_path) == "hello world"
137-
137+
138138
# Cleanup
139139
File.rm!(temp_path)
140140
end
141141

142142
test "write creates directory if needed" do
143143
temp_dir = Path.join(System.tmp_dir!(), "nested_test_dir")
144144
temp_path = Path.join(temp_dir, "test_file.txt")
145-
145+
146146
response = %HTTP.Response{
147147
status: 200,
148148
headers: %HTTP.Headers{},
@@ -153,14 +153,14 @@ defmodule HTTP.ResponseTest do
153153

154154
assert :ok = HTTP.Response.write_to(response, temp_path)
155155
assert File.read!(temp_path) == "nested directory content"
156-
156+
157157
# Cleanup
158158
File.rm_rf!(temp_dir)
159159
end
160160

161161
test "write_to with actual HTTP response" do
162162
temp_path = Path.join(System.tmp_dir!(), "actual_response_test.txt")
163-
163+
164164
resp =
165165
HTTP.fetch("https://httpbin.org/json",
166166
headers: [{"user-agent", "Elixir http_fetch 0.4.1"}],
@@ -170,13 +170,13 @@ defmodule HTTP.ResponseTest do
170170

171171
assert resp.status == 200
172172
assert :ok = HTTP.Response.write_to(resp, temp_path)
173-
173+
174174
# Verify file was written
175175
assert File.exists?(temp_path)
176176
content = File.read!(temp_path)
177177
assert byte_size(content) > 0
178178
assert content =~ "slideshow"
179-
179+
180180
# Cleanup
181181
File.rm!(temp_path)
182182
end

0 commit comments

Comments
 (0)