servicestack 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +16 -0
- data/lib/servicestack/json_service_client.rb +93 -5
- data/lib/servicestack/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 991af3e1951fbd5467476753e01f05db5992e7399cb0ac3ca0acfaf909056b8e
|
|
4
|
+
data.tar.gz: 0e2dcc59cb68a58911ba105d82ff245aff0001a5e41870324c5c2cd12d31e2f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4c822aad83ce16504e03018f13112a36b3c3added39d5f3d52c3dd5234c9d20bcadf03e8dad3eb8f65c671359c5b7d50e53277ae46304ac8fb68f186bd08c7a
|
|
7
|
+
data.tar.gz: 84eadaa862a26af3237499a7d07682243b61ed5b97314ed3f498002e94c2360c938186ff90b423e7a6b4e3dbf404b5cf1323b5b4e318804e7fbdc48f7ac29b0d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.1]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `post_file_with_request` and `post_files_with_request` for uploading files with
|
|
10
|
+
a Request DTO as a `multipart/form-data` Request, incl. an `UploadFile` that
|
|
11
|
+
accepts file contents as a String or any IO
|
|
12
|
+
- `post_files_with_request_url` for uploading files to a custom URL
|
|
13
|
+
|
|
5
14
|
## [0.1.0]
|
|
6
15
|
|
|
7
16
|
### Added
|
|
@@ -17,4 +26,5 @@ All notable changes to this project will be documented in this file.
|
|
|
17
26
|
- Built-in ServiceStack DTOs referenced by generated DTOs (`ResponseStatus`,
|
|
18
27
|
`QueryBase`, `QueryResponse`, `Authenticate`, ...)
|
|
19
28
|
|
|
29
|
+
[0.1.1]: https://github.com/ServiceStack/servicestack-ruby/releases/tag/v0.1.1
|
|
20
30
|
[0.1.0]: https://github.com/ServiceStack/servicestack-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -174,6 +174,22 @@ Or send a Request to a one-way endpoint that ignores its Response:
|
|
|
174
174
|
client.publish(Hello.new(name: 'World'))
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
### Uploading Files
|
|
178
|
+
|
|
179
|
+
Use `post_file_with_request` to upload a file with an API Request, whose contents
|
|
180
|
+
can be a String or any IO:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
res = File.open('photo.png', 'rb') do |file|
|
|
184
|
+
client.post_file_with_request(UploadPhoto.new(album: 'Holiday'),
|
|
185
|
+
ServiceStack::UploadFile.new(field_name: 'file', file_name: 'photo.png',
|
|
186
|
+
content_type: 'image/png', stream: file))
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The Request DTO's populated properties are sent as form fields alongside the file.
|
|
191
|
+
To upload multiple files use `post_files_with_request`.
|
|
192
|
+
|
|
177
193
|
### Custom URLs
|
|
178
194
|
|
|
179
195
|
```ruby
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'net/http'
|
|
5
|
+
require 'securerandom'
|
|
5
6
|
require 'uri'
|
|
6
7
|
require_relative 'dto'
|
|
7
8
|
require_relative 'types'
|
|
@@ -19,6 +20,29 @@ module ServiceStack
|
|
|
19
20
|
HEAD = 'HEAD'
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
# A file uploaded in a multipart/form-data Request.
|
|
24
|
+
#
|
|
25
|
+
# Its contents can be supplied as a String or any IO that responds to `read`,
|
|
26
|
+
# e.g. a File or StringIO.
|
|
27
|
+
class UploadFile
|
|
28
|
+
attr_accessor :field_name, :file_name, :content_type, :stream
|
|
29
|
+
|
|
30
|
+
def initialize(field_name: 'file', file_name: nil, content_type: nil, stream: nil)
|
|
31
|
+
@field_name = field_name
|
|
32
|
+
@file_name = file_name
|
|
33
|
+
@content_type = content_type
|
|
34
|
+
@stream = stream
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The file contents to upload.
|
|
38
|
+
def contents
|
|
39
|
+
return @stream unless @stream.respond_to?(:read)
|
|
40
|
+
|
|
41
|
+
@stream.binmode if @stream.respond_to?(:binmode)
|
|
42
|
+
@stream.read
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
22
46
|
# Either the typed Response of a successful API Request or the structured
|
|
23
47
|
# ResponseStatus error of a failed one, returned by `api`.
|
|
24
48
|
class ApiResult
|
|
@@ -203,6 +227,42 @@ module ServiceStack
|
|
|
203
227
|
execute(method, to_absolute_url(path), body, args: args)
|
|
204
228
|
end
|
|
205
229
|
|
|
230
|
+
# ── File Uploads ──
|
|
231
|
+
|
|
232
|
+
# Uploads a file with a Request DTO as a multipart/form-data Request,
|
|
233
|
+
# returning its typed Response, e.g:
|
|
234
|
+
#
|
|
235
|
+
# File.open('photo.png', 'rb') do |file|
|
|
236
|
+
# client.post_file_with_request(UploadPhoto.new(album: 'Holiday'),
|
|
237
|
+
# ServiceStack::UploadFile.new(field_name: 'file', file_name: 'photo.png',
|
|
238
|
+
# content_type: 'image/png', stream: file))
|
|
239
|
+
# end
|
|
240
|
+
def post_file_with_request(request, file, args: nil)
|
|
241
|
+
post_files_with_request(request, [file], args: args)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Uploads multiple files with a Request DTO as a multipart/form-data Request.
|
|
245
|
+
def post_files_with_request(request, files, args: nil)
|
|
246
|
+
url = create_url_from_dto(HttpMethods::POST, request)
|
|
247
|
+
post_files_with_request_url(url, request, files, response_as: resolve_response_type(request), args: args)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Uploads files with a Request DTO to a custom relative path or absolute URL.
|
|
251
|
+
def post_files_with_request_url(path, request, files, response_as: nil, args: nil)
|
|
252
|
+
boundary = "----ServiceStackFormBoundary#{SecureRandom.hex(12)}"
|
|
253
|
+
body = multipart_body(boundary, request, files)
|
|
254
|
+
|
|
255
|
+
json = execute(HttpMethods::POST, to_absolute_url(path), body, args: args,
|
|
256
|
+
content_type: "multipart/form-data; boundary=#{boundary}")
|
|
257
|
+
return nil if response_as.nil?
|
|
258
|
+
return json if response_as == String
|
|
259
|
+
|
|
260
|
+
parsed = json.to_s.strip.empty? ? {} : JSON.parse(json)
|
|
261
|
+
return parsed unless response_as.respond_to?(:from_hash)
|
|
262
|
+
|
|
263
|
+
response_as.from_hash(parsed)
|
|
264
|
+
end
|
|
265
|
+
|
|
206
266
|
# Converts a relative path into an absolute URL of this client.
|
|
207
267
|
def to_absolute_url(path_or_url)
|
|
208
268
|
return path_or_url if path_or_url.to_s.start_with?('http://', 'https://')
|
|
@@ -232,11 +292,11 @@ module ServiceStack
|
|
|
232
292
|
response_type.from_hash(parsed)
|
|
233
293
|
end
|
|
234
294
|
|
|
235
|
-
def execute(method, url, body, args: nil, retry_on_auth_failure: true)
|
|
295
|
+
def execute(method, url, body, args: nil, retry_on_auth_failure: true, content_type: nil)
|
|
236
296
|
url = append_query_string(url, args) if args && !args.empty?
|
|
237
297
|
|
|
238
298
|
uri = URI.parse(url)
|
|
239
|
-
request = new_http_request(method, uri, body)
|
|
299
|
+
request = new_http_request(method, uri, body, content_type: content_type)
|
|
240
300
|
|
|
241
301
|
@request_filter&.call(request)
|
|
242
302
|
self.class.global_request_filter&.call(request)
|
|
@@ -250,7 +310,7 @@ module ServiceStack
|
|
|
250
310
|
|
|
251
311
|
status_code = response.code.to_i
|
|
252
312
|
if status_code == 401 && retry_on_auth_failure && refresh_access_token
|
|
253
|
-
return execute(method, url, body, retry_on_auth_failure: false)
|
|
313
|
+
return execute(method, url, body, retry_on_auth_failure: false, content_type: content_type)
|
|
254
314
|
end
|
|
255
315
|
|
|
256
316
|
# Redirects aren't followed, e.g. Services that redirect to a HTML sign in
|
|
@@ -264,7 +324,7 @@ module ServiceStack
|
|
|
264
324
|
raise WebServiceException.new(e.message, inner_exception: e)
|
|
265
325
|
end
|
|
266
326
|
|
|
267
|
-
def new_http_request(method, uri, body)
|
|
327
|
+
def new_http_request(method, uri, body, content_type: nil)
|
|
268
328
|
request_class = case method.to_s.upcase
|
|
269
329
|
when HttpMethods::GET then Net::HTTP::Get
|
|
270
330
|
when HttpMethods::POST then Net::HTTP::Post
|
|
@@ -288,7 +348,7 @@ module ServiceStack
|
|
|
288
348
|
request['Cookie'] = @cookies.map { |k, v| "#{k}=#{v}" }.join('; ') unless @cookies.empty?
|
|
289
349
|
|
|
290
350
|
if body && has_request_body?(method)
|
|
291
|
-
request['Content-Type']
|
|
351
|
+
request['Content-Type'] = content_type || request['Content-Type'] || MIME_TYPE_JSON
|
|
292
352
|
request.body = body.is_a?(String) ? body : JSON.generate(to_hash(body))
|
|
293
353
|
end
|
|
294
354
|
|
|
@@ -402,6 +462,34 @@ module ServiceStack
|
|
|
402
462
|
dto
|
|
403
463
|
end
|
|
404
464
|
|
|
465
|
+
# Builds the multipart/form-data body of a file upload Request, sending the
|
|
466
|
+
# populated Request DTO properties as form fields
|
|
467
|
+
def multipart_body(boundary, request, files)
|
|
468
|
+
body = +''
|
|
469
|
+
|
|
470
|
+
to_hash(request).each do |name, value|
|
|
471
|
+
next if value.nil?
|
|
472
|
+
|
|
473
|
+
body << "--#{boundary}\r\n"
|
|
474
|
+
body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
|
|
475
|
+
body << "#{qs_value(value)}\r\n"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
files.each do |file|
|
|
479
|
+
field_name = file.field_name.to_s.empty? ? 'file' : file.field_name
|
|
480
|
+
file_name = file.file_name.to_s.empty? ? 'file' : file.file_name
|
|
481
|
+
|
|
482
|
+
body << "--#{boundary}\r\n"
|
|
483
|
+
body << "Content-Disposition: form-data; name=\"#{field_name}\"; filename=\"#{file_name}\"\r\n"
|
|
484
|
+
body << "Content-Type: #{file.content_type || 'application/octet-stream'}\r\n\r\n"
|
|
485
|
+
body << file.contents.to_s.dup.force_encoding(Encoding::BINARY)
|
|
486
|
+
body << "\r\n"
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
body << "--#{boundary}--\r\n"
|
|
490
|
+
body.force_encoding(Encoding::BINARY)
|
|
491
|
+
end
|
|
492
|
+
|
|
405
493
|
def has_request_body?(method)
|
|
406
494
|
!%w[GET DELETE HEAD OPTIONS].include?(method.to_s.upcase)
|
|
407
495
|
end
|
data/lib/servicestack/version.rb
CHANGED