無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

HTTPアクセスユーティリティ

HTTPアクセスをスタブ風?に行うためのユーティリティを書いてみました。

やりたかったこと
  • 接続先URLやパラメータ、プロキシ設定を一箇所にまとめたい。
    • HTTPアクセスで使用するホスト名、パス、ポートなど接続先の情報は、共通のYAMLファイルに書きます。
    • プロキシ設定も、スタブ全体の設定としてYAMLファイルで記述します。
  • スタブのメソッド呼び出しで通信実行
    • 「stub.list()」で、"list"に関連付けられているURLにアクセスし、結果を返します。
使い方

設定ファイルを書きます。

---
# コンフィグレーション
conf:
  #proxy_host: <プロキシホスト>
  #proxy_port: <プロキシポート>

# デフォルトの接続先設定
# 個別の設定で設定されなかった値は、ここでの指定が使われる。
default:
  host: www.google.co.jp
  path: /search
  port: 80
  method: "get" # "post" or "get"
  headers: 
    # hoo: var

# メソッド名に対応する接続先の設定
requests:
  get_yahoo: # メソッド名
      host: yahoo.co.jp
      path: ""

  search:
    params:
      q: "" # 検索条件

スタブを生成してメソッドを実行します。

require 'http-requestor'

# スタブを生成 / 引数で設定ファイルのパスを指定
stub = HttpRequestor.new("./request.yaml")

# 設定ファイルの get_yahoo に対応するURLにアクセスし、結果を返す
# 戻り値はNet::HTTPResponse
response = stub.get_yahoo()
puts response.code
puts response.body

# ブロックを指定して接続先情報をカスタマイズできる。
response = stub.search(){ |request|
  request["params"]["q"] = "hoo"
}
puts response.body
実装
require "net/http"
require 'yaml'
require 'cgi'

class HttpRequestor

  # 初期化する。
  def initialize( request_file="./request.yaml" )
    File.open( request_file ) {|f|
      @request = YAML.load( f )
    }
  end

  # メソッドが定義されていない場合に呼ばれる。
  def method_missing( name, *args, &block )
    return do_request( name.to_s, &block )
  end

  # リクエストを実行する。
  # ブロックが与えられた場合は、ブロックにリクエスト情報を渡し編集する。
  def do_request ( method, &block )
    fail "unknown method " << method unless @request["requests"].key?( method )
    request = merge( {"params"=>{}, "headers"=>{}}, @request["requests"][method] )
    request = merge( request, @request["default"] )
    block.call( request ) if block_given?

    response = nil
    Net::HTTP.version_1_2

    requestor = nil
    if ( @request["conf"] != nil &&  @request["conf"]["proxy_host"] != nil && @request["conf"]["proxy_port"] != nil )
      requestor = Net::HTTP::Proxy(@request["conf"]["proxy_host"], @request["conf"]["proxy_port"])
    else
      requestor = Net::HTTP
    end
    requestor.start( request["host"], request["port"] ) { |http|
      param = map_to_param( request["params"] )
      param += "&" + list_to_param(request["param-list"])
      if request["method"] == "get"
        response = http.get( request["path"] + "?" + param, request["headers"] )
      elsif request["method"] == "post"
        # POSTの場合、bodyの指定があればそちらを優先する。
        param = request["body"] != nil ?  request["body"] : param
        response = http.post( request["path"], param, request["headers"] )
      end
    }
    return response
  end

private
  def map_to_param( map )
   return "" if map == nil
    str = ""
    map.each { | key, value |
      str << key << '=' << CGI.escape(value.to_s) << '&' if value != nil
    }
    return str
  end

  def list_to_param( list )
   return "" if list == nil
    str = ""
    list.each { | item |
      if item["key"] != nil && item["value"] != nil
        str << item["key"] << '=' << CGI.escape(item["value"].to_s) << '&'
      end
    }
    return str
  end

  # リクエストの設定をマージする。
  def merge (request, parent)
    return request if ( parent == nil )
    request.merge!(parent) { |key, self_val, other_val|
      if ( key == "headers" || key == "params" )
        merge(self_val != nil ? self_val : {}, other_val)
      else
        self_val != nil ? self_val : other_val
      end
    }
    return request
  end

end