@@ -52,15 +52,20 @@ if Code.ensure_loaded?(Finch) do
52
52
@ behaviour Tesla.Adapter
53
53
alias Tesla.Multipart
54
54
55
+ @ defaults [
56
+ receive_timeout: 15_000
57
+ ]
58
+
55
59
@ impl Tesla.Adapter
56
60
def call ( % Tesla.Env { } = env , opts ) do
57
- opts = Tesla.Adapter . opts ( env , opts )
61
+ opts = Tesla.Adapter . opts ( @ defaults , env , opts )
58
62
59
63
name = Keyword . fetch! ( opts , :name )
60
64
url = Tesla . build_url ( env . url , env . query )
61
65
req_opts = Keyword . take ( opts , [ :pool_timeout , :receive_timeout ] )
66
+ req = build ( env . method , url , env . headers , env . body )
62
67
63
- case request ( name , env . method , url , env . headers , env . body , req_opts ) do
68
+ case request ( req , name , req_opts , opts ) do
64
69
{ :ok , % Finch.Response { status: status , headers: headers , body: body } } ->
65
70
{ :ok , % Tesla.Env { env | status: status , headers: headers , body: body } }
66
71
@@ -69,20 +74,72 @@ if Code.ensure_loaded?(Finch) do
69
74
end
70
75
end
71
76
72
- defp request ( name , method , url , headers , % Multipart { } = mp , opts ) do
77
+ defp build ( method , url , headers , % Multipart { } = mp ) do
73
78
headers = headers ++ Multipart . headers ( mp )
74
79
body = Multipart . body ( mp ) |> Enum . to_list ( )
75
80
76
- request ( name , method , url , headers , body , opts )
81
+ build ( method , url , headers , body )
82
+ end
83
+
84
+ defp build ( method , url , headers , % Stream { } = body_stream ) do
85
+ build ( method , url , headers , { :stream , body_stream } )
77
86
end
78
87
79
- defp request ( _name , _method , _url , _headers , % Stream { } , _opts ) do
80
- raise "Streaming is not supported by this adapter!"
88
+ defp build ( method , url , headers , body_stream_fun ) when is_function ( body_stream_fun ) do
89
+ build ( method , url , headers , { :stream , body_stream_fun } )
81
90
end
82
91
83
- defp request ( name , method , url , headers , body , opts ) do
92
+ defp build ( method , url , headers , body ) do
84
93
Finch . build ( method , url , headers , body )
85
- |> Finch . request ( name , opts )
94
+ end
95
+
96
+ defp request ( req , name , req_opts , opts ) do
97
+ case opts [ :response ] do
98
+ :stream -> stream ( req , name , req_opts )
99
+ nil -> Finch . request ( req , name , req_opts )
100
+ other -> raise "Unknown response option: #{ inspect ( other ) } "
101
+ end
102
+ end
103
+
104
+ defp stream ( req , name , opts ) do
105
+ owner = self ( )
106
+ ref = make_ref ( )
107
+
108
+ fun = fn
109
+ { :status , status } , _acc -> status
110
+ { :headers , headers } , status -> send ( owner , { ref , { :status , status , headers } } )
111
+ { :data , data } , _acc -> send ( owner , { ref , { :data , data } } )
112
+ end
113
+
114
+ task =
115
+ Task . async ( fn ->
116
+ case Finch . stream ( req , name , nil , fun , opts ) do
117
+ { :ok , _acc } -> send ( owner , { ref , :eof } )
118
+ { :error , error } -> send ( owner , { ref , { :error , error } } )
119
+ end
120
+ end )
121
+
122
+ receive do
123
+ { ^ ref , { :status , status , headers } } ->
124
+ body =
125
+ Stream . unfold ( nil , fn _ ->
126
+ receive do
127
+ { ^ ref , { :data , data } } ->
128
+ { data , nil }
129
+
130
+ { ^ ref , :eof } ->
131
+ Task . await ( task )
132
+ nil
133
+ after
134
+ opts [ :receive_timeout ] -> nil
135
+ end
136
+ end )
137
+
138
+ { :ok , % Finch.Response { status: status , headers: headers , body: body } }
139
+ after
140
+ opts [ :receive_timeout ] ->
141
+ { :error , :timeout }
142
+ end
86
143
end
87
144
end
88
145
end
0 commit comments