@@ -69,13 +69,19 @@ class HTTP::Client
6969 # ```
7070 getter port : Int32
7171
72- # Returns whether this client is using SSL.
72+ # If this client uses SSL, returns its `OpenSSL::SSL::Context`, raises otherwise.
73+ #
74+ # Changes made after the initial request will have no effect.
7375 #
7476 # ```
7577 # client = HTTP::Client.new "www.example.com", ssl: true
76- # client.ssl? # => true
78+ # client.ssl # => #<OpenSSL::SSL::Context ...>
7779 # ```
78- getter? ssl : Bool
80+ ifdef without_openssl
81+ getter! ssl : Nil
82+ else
83+ getter! ssl : OpenSSL ::SSL ::Context ?
84+ end
7985
8086 # Whether automatic compression/decompression is enabled.
8187 property? compress : Bool
@@ -93,16 +99,32 @@ class HTTP::Client
9399 # Creates a new HTTP client with the given *host*, *port* and *ssl*
94100 # configurations. If no port is given, the default one will
95101 # be used depending on the *ssl* arguments: 80 for if *ssl* is `false`,
96- # 443 if *ssl* is `true`.
97- def initialize (@host , port = nil , @ssl = false )
98- ifdef without_openssl
99- if @ssl
102+ # 443 if *ssl* is truthy. If *ssl* is `true` a new `OpenSSL::SSL::Context` will
103+ # be used, else the given one. In any case the active context can be accessed through `ssl`.
104+ ifdef without_openssl
105+ def initialize (@host , port = nil , ssl : Bool = false )
106+ @ssl = nil
107+ if ssl
100108 raise " HTTP::Client ssl is disabled because `-D without_openssl` was passed at compile time"
101109 end
102- end
103110
104- @port = (port || (ssl ? 443 : 80 )).to_i
105- @compress = true
111+ @port = (port || (@ssl ? 443 : 80 )).to_i
112+ @compress = true
113+ end
114+ else
115+ def initialize (@host , port = nil , ssl : Bool | OpenSSL ::SSL ::Context = false )
116+ @ssl = case ssl
117+ when true
118+ OpenSSL ::SSL ::Context .new_for_client
119+ when OpenSSL ::SSL ::Context
120+ ssl
121+ when false
122+ nil
123+ end
124+
125+ @port = (port || (@ssl ? 443 : 80 )).to_i
126+ @compress = true
127+ end
106128 end
107129
108130 # Creates a new HTTP client from a URI. Parses the *host*, *port*,
@@ -120,10 +142,14 @@ class HTTP::Client
120142 # This constructor will *ignore* any path or query segments in the URI
121143 # as those will need to be passed to the client when a request is made.
122144 #
145+ # If *ssl* is given it will be used, if not a new SSL context will be created.
146+ # If *ssl* is given and *uri* is a HTTP URI, `ArgumentError` is raised.
147+ # In any case the active context can be accessed through `ssl`.
148+ #
123149 # This constructor will raise an exception if any scheme but HTTP or HTTPS
124150 # is used.
125- def self.new (uri : URI )
126- ssl = ssl_flag(uri)
151+ def self.new (uri : URI , ssl = nil )
152+ ssl = ssl_flag(uri, ssl )
127153 host = validate_host(uri)
128154 new(host, uri.port, ssl)
129155 end
@@ -296,8 +322,8 @@ class HTTP::Client
296322 # response = HTTP::Client.{{method.id}}("/", headers: HTTP::Headers{"User-agent": "AwesomeApp"}, body: "Hello!")
297323 # response.body #=> "..."
298324 # ```
299- def self. {{method.id}}(url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil ) : HTTP ::Client ::Response
300- exec {{method.upcase}}, url, headers, body
325+ def self. {{method.id}}(url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil , ssl = nil ) : HTTP ::Client ::Response
326+ exec {{method.upcase}}, url, headers, body, ssl
301327 end
302328
303329 # Executes a {{method.id.upcase}} request and yields the response to the block.
@@ -308,8 +334,8 @@ class HTTP::Client
308334 # response.body_io.gets #=> "..."
309335 # end
310336 # ```
311- def self. {{method.id}}(url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil )
312- exec {{method.upcase}}, url, headers, body do |response |
337+ def self. {{method.id}}(url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil , ssl = nil )
338+ exec {{method.upcase}}, url, headers, body, ssl do |response |
313339 yield response
314340 end
315341 end
@@ -351,8 +377,8 @@ class HTTP::Client
351377 # ```
352378 # response = HTTP::Client.post_form "http://www.example.com", "foo=bar"
353379 # ```
354- def self.post_form (url, form : String | Hash , headers : HTTP ::Headers ? = nil ) : HTTP ::Client ::Response
355- exec(url) do |client , path |
380+ def self.post_form (url, form : String | Hash , headers : HTTP ::Headers ? = nil , ssl = nil ) : HTTP ::Client ::Response
381+ exec(url, ssl ) do |client , path |
356382 client.post_form(path, form, headers)
357383 end
358384 end
@@ -455,8 +481,8 @@ class HTTP::Client
455481 # response = HTTP::Client.exec "GET", "http://www.example.com"
456482 # response.body # => "..."
457483 # ```
458- def self.exec (method, url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil ) : HTTP ::Client ::Response
459- exec(url) do |client , path |
484+ def self.exec (method, url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil , ssl = nil ) : HTTP ::Client ::Response
485+ exec(url, ssl ) do |client , path |
460486 client.exec method, path, headers, body
461487 end
462488 end
@@ -469,8 +495,8 @@ class HTTP::Client
469495 # response.body_io.gets # => "..."
470496 # end
471497 # ```
472- def self.exec (method, url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil )
473- exec(url) do |client , path |
498+ def self.exec (method, url : String | URI , headers : HTTP ::Headers ? = nil , body : String ? = nil , ssl = nil )
499+ exec(url, ssl ) do |client , path |
474500 client.exec(method, path, headers, body) do |response |
475501 yield response
476502 end
@@ -503,8 +529,8 @@ class HTTP::Client
503529 @socket = socket
504530
505531 ifdef ! without_openssl
506- if @ssl
507- ssl_socket = OpenSSL ::SSL ::Socket .new(socket, sync_close: true , hostname: @host )
532+ if ssl = @ssl
533+ ssl_socket = OpenSSL ::SSL ::Socket .new(socket, context: ssl, sync_close: true , hostname: @host )
508534 @socket = socket = ssl_socket
509535 end
510536 end
@@ -520,30 +546,50 @@ class HTTP::Client
520546 end
521547 end
522548
523- private def self.exec (string : String )
549+ private def self.exec (string : String , ssl = nil )
524550 uri = URI .parse(string)
525551
526552 unless uri.scheme && uri.host
527553 # Assume http if no scheme and host are specified
528554 uri = URI .parse(" http://#{ string } " )
529555 end
530556
531- exec(uri) do |client , path |
557+ exec(uri, ssl ) do |client , path |
532558 yield client, path
533559 end
534560 end
535561
536- protected def self.ssl_flag (uri )
537- scheme = uri.scheme
538- case scheme
539- when nil
540- raise ArgumentError .new(" missing scheme: #{ uri } " )
541- when " http"
542- false
543- when " https"
544- true
545- else
546- raise ArgumentError .new " Unsupported scheme: #{ scheme } "
562+ ifdef without_openssl
563+ protected def self.ssl_flag (uri , context : Nil )
564+ scheme = uri.scheme
565+ case scheme
566+ when nil
567+ raise ArgumentError .new(" missing scheme: #{ uri } " )
568+ when " http"
569+ false
570+ when " https"
571+ true
572+ else
573+ raise ArgumentError .new " Unsupported scheme: #{ scheme } "
574+ end
575+ end
576+ else
577+ protected def self.ssl_flag (uri , context : OpenSSL ::SSL ::Context ?)
578+ scheme = uri.scheme
579+ case {scheme, context}
580+ when {nil , _}
581+ raise ArgumentError .new(" missing scheme: #{ uri } " )
582+ when {" http" , nil }
583+ false
584+ when {" http" , OpenSSL ::SSL ::Context }
585+ raise ArgumentError .new(" SSL context given for HTTP URI" )
586+ when {" https" , nil }
587+ true
588+ when {" https" , OpenSSL ::SSL ::Context }
589+ context
590+ else
591+ raise ArgumentError .new " Unsupported scheme: #{ scheme } "
592+ end
547593 end
548594 end
549595
@@ -554,8 +600,8 @@ class HTTP::Client
554600 raise ArgumentError .new %( Request URI must have host (URI is: #{uri}))
555601 end
556602
557- private def self.exec (uri : URI )
558- ssl = ssl_flag(uri)
603+ private def self.exec (uri : URI , ssl = nil )
604+ ssl = ssl_flag(uri, ssl )
559605 host = validate_host(uri)
560606
561607 port = uri.port
0 commit comments