require 'base64' require 'openssl' require 'net/http' require 'json' module Zzlink # Error. class Error < StandardError def initialize(code, error) super("#{code}: #{error}") end end # The HTTP client. module Client class << self attr_accessor :access_key, :secret_key, :api_url end # Do a GET request. def get(path, query_params = {}) uri = uri_of(path, query_params) resp = Net::HTTP.start(uri.host, uri.port, nil, nil, nil, nil, use_ssl: (uri.scheme == 'https')) do |http| req = Net::HTTP::Get.new(uri.request_uri) set_request_headers(req) http.request(req) end check_and_return(resp) end def post(path, data = {}) uri = uri_of(path) resp = Net::HTTP.start(uri.host, uri.port, nil, nil, nil, nil, use_ssl: (uri.scheme == 'https')) do |http| req = Net::HTTP::Post.new(uri.request_uri) set_request_headers(req) unless data.empty? req['Content-Type'] = 'application/json' req.body = data.to_json end http.request(req) end check_and_return(resp) end def put(path, data = {}) uri = uri_of(path) resp = Net::HTTP.start(uri.host, uri.port, nil, nil, nil, nil, use_ssl: (uri.scheme == 'https')) do |http| req = Net::HTTP::Put.new(uri.request_uri) set_request_headers(req) unless data.empty? req['Content-Type'] = 'application/json' req.body = data.to_json end http.request(req) end check_and_return(resp) end def delete(path, query_params = {}) uri = uri_of(path, query_params) resp = Net::HTTP.start(uri.host, uri.port, nil, nil, nil, nil, use_ssl: (uri.scheme == 'https')) do |http| req = Net::HTTP::Delete.new(uri.request_uri) set_request_headers(req) http.request(req) end check_and_return(resp) end private # Make URI object from request path and query parameters. # @param path [String] request path (without scheme, host and port). # @param query_params [Hash] optional query parameters. # @return the URI object. def uri_of(path, query_params = {}) url = Client.api_url + path if query_params.empty? URI(url) else queries = URI.encode_www_form(query_params) url.include?('?') ? URI(url + '&' + queries) : URI(url + '?' + queries) end end # Set headers for the HTTP request. # @param request [HttpRequest] the request object. def set_request_headers(request) host = self.name + '.' + self.version date = Time.now().gmtime.strftime('%a, %d %b %Y %H:%M:%S %Z') signing_string = "host: #{host}\n#{request.method} #{request.path} HTTP/1.1\ndate: #{date}" signature = Base64.strict_encode64(OpenSSL::HMAC.digest("SHA256", Client.secret_key, signing_string)) request['Accept'] = 'application/json' request['Host'] = host request['Date'] = date request['Authorization'] = "hmac username=\"#{Client.access_key}\", algorithm=\"hmac-sha256\", headers=\"host request-line date\", signature=\"#{signature}\"" end def check_and_return(response) if response.is_a?(Net::HTTPSuccess) response.body else raise Error.new(response.code, response.body) end end end class << self # Initialize the ZzLink client. # @param access_key [String] developer's access key. # @param secret_key [String] developer's secret key. # @param api_url [String] URL of zzLink API service. Default to 'https://api.zhizhi.link'. def init(access_key, secret_key, api_url = nil) Client.access_key = access_key Client.secret_key = secret_key Client.api_url = (api_url.nil? || api_url.empty?) ? 'https://api.zhizhi.link' : api_url.delete_suffix('/') end end end