sizeとcountの違い
activerecordで取得したレコードの数を調べるときにcountとsizeを使ったら速度にかなりの差が出たのでその違いを調べました。
countとsizeの差はそのレコードがロードされているか否かで変わります。
レコードがロードされている時、countはsqlを生成し、sizeはsqlを生成しません。つまり、sizeの方が高速です。
a = Article.first.tags a.count (0.4ms) SELECT COUNT(*) FROM `tags` INNER JOIN `articles_tags` ON `tags`.`id` = `articles_tags`.`tag_id` WHERE `articles_tags`.`article_id` = 1 => 1 a.size => 1
レコードがロードされていない時はどちらもcountのsqlを生成します。
Article.first.tags.count Article Load (0.3ms) SELECT `articles`.* FROM `articles` ORDER BY `articles`.`id` ASC LIMIT 1 (0.5ms) SELECT COUNT(*) FROM `tags` INNER JOIN `articles_tags` ON `tags`.`id` = `articles_tags`.`tag_id` WHERE `articles_tags`.`article_id` = 1 => 1 Article.first.tags.size Article Load (0.3ms) SELECT `articles`.* FROM `articles` ORDER BY `articles`.`id` ASC LIMIT 1 (0.3ms) SELECT COUNT(*) FROM `tags` INNER JOIN `articles_tags` ON `tags`.`id` = `articles_tags`.`tag_id` WHERE `articles_tags`.`article_id` = 1 => 1
条件を付けたい時はcountを使い、他はだいたいsizeで事足りるようです。
参考
http://apidock.com/rails/ActiveRecord/Associations/AssociationCollection/size
httpレスポンスに自作ヘッダーを付与する
小ネタです。
httpレスポンスに自作ヘッダーを付与します。
phpならただ
<?php header('Name: asmsuechan'); ?>
とすればレスポンスのヘッダーにName: asmsuechanを追加できます。ここで注意すべきは、<?php ~ ?>より以前に何か文字を配置しないことです。
herokuでサクっと作って試してみました。https://my-http-response.herokuapp.com/
% ruby http_client.rb HTTP/1.1 200 OK Connection: close Date: Sat, 30 Apr 2016 14:35:19 GMT Server: Apache Name: asmsuechan Content-Length: 0 Content-Type: text/html;charset=UTF-8 Via: 1.1 vegur
Name: asmsuechanが返ってきています。
参考
httpクライアントの実装(2)
続きです。
コードをいじりました。
レスポンスをただputsするのではなくてResponseクラスのインスタンスを返すようにしました。
#response.rb class Response attr_accessor :request, :headers, :response_except_body, :status, :body def initialize(response) @response = response @body = @response.slice!(/(\n\r\n).+/m) @response_except_body = @response.slice(/.+/m) #なぜsliceしないと動かないのだろうか・・・・ @request = @response.slice!(/.+/) @status = @request.slice!(/\d{3}.+/) #ヘッダーの切り出し @headers = [] while @response != "" header = @response.slice!(/.+/) @headers.push(header) unless header.nil? @response.sub!(/(\r\n|\r|\n)/, '') end #切り出したヘッダーからインスタンス変数、 #アクセサメソッドを作成 @headers.each do |header| header_name_raw = header.slice!(/^.+: /) unless header_name_raw.nil? header_name = header_name_raw.downcase .gsub("-", "_") .gsub(":", "") .gsub(" ", "") self.instance_variable_set("@#{header_name}",header) add_accessor header_name end end end def all_response self.response_except_body end def disp_body self.body end private def add_accessor instance_variable_name self.class.send(:attr_accessor, instance_variable_name) end end
#http_client.rb require 'socket' require './response' class HttpClient attr_accessor :uri, :port, :path def initialize(options = {}) @uri = options[:uri] @path = options[:path] @port = options[:port] end [:get, :post, :put, :delete, :head, :options].each do |method| define_method(method) do |content = nil, type = "text/plain"| @socket = TCPSocket.open(uri, port) @socket << "#{method.to_s.swapcase} #{self.path} HTTP/1.1\r\n" @socket << "Host: #{self.uri}\r\n" @socket << "Content-Type: #{type}; charset=utf-8\r\n" unless content.nil? @socket << "Content-Length: #{content.length}\r\n" @socket << "\r\n#{content}" end @socket << "Connection: close\r\n" @socket << "\r\n" response = Response.new(@socket.read) return response @socket.close end end end request = HttpClient.new(uri:"example.com", path:"/", port:80) puts "====================" puts request.get.all_response puts "====================" puts HttpClient.new(uri:"example.com", path:"/", port:80).post("こんにちは").all_response puts "====================" puts HttpClient.new(uri:"www.google.com", path:"/", port:80).post("こんにちは").all_response puts "====================" puts HttpClient.new(uri:"example.com", path:"/", port:80).options.all_response puts "===================="
実行すると、
% ruby http_client.rb ==================== HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: max-age=604800 Content-Type: text/html Date: Sat, 30 Apr 2016 03:37:44 GMT Etag: "359670651" Expires: Sat, 07 May 2016 03:37:44 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: ECS (rhv/818F) Vary: Accept-Encoding X-Cache: HIT x-ec-custom-error: 1 Content-Length: 1270 Connection: close ==================== HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: max-age=604800 Content-Type: text/html Date: Sat, 30 Apr 2016 03:37:44 GMT Etag: "359670651" Expires: Sat, 07 May 2016 03:37:44 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: EOS (lax004/2812) Content-Length: 1270 ==================== HTTP/1.1 405 Method Not Allowed Allow: GET, HEAD Date: Sat, 30 Apr 2016 03:37:44 GMT Content-Type: text/html; charset=UTF-8 Server: gws Content-Length: 1589 X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN ==================== HTTP/1.1 200 OK Allow: OPTIONS, GET, HEAD, POST Cache-Control: max-age=604800 Date: Sat, 30 Apr 2016 03:37:45 GMT Expires: Sat, 07 May 2016 03:37:45 GMT Server: EOS (lax004/28A4) x-ec-custom-error: 1 Content-Length: 0 Connection: close ====================
簡単にResponseクラスの説明をすると、正規表現でガリガリ削ってレスポンスのヘッダーを動的にインスタンス変数に入れて動的にアクセサメソッド付与しています。これだけです。
参考
HTTPクライアントの実装
Webを支える技術を読んでいます。
Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)
- 作者: 山本陽平
- 出版社/メーカー: 技術評論社
- 発売日: 2010/04/08
- メディア: 単行本(ソフトカバー)
- 購入: 143人 クリック: 4,320回
- この商品を含むブログ (181件) を見る
Webエンジニアなら知ってて当然の基礎の基礎が載っている本です。
この前基礎の基礎を聞かれた時ハッキリと答えられなくて悔しかったので読んでいます。
今回はhttpリクエスト周りです。
httpリクエスト投げるコード書いた
オブジェクト指向っぽく書きました。(書いたつもりです)
optionsメソッド投げて返ってきたAllowed Methodから動的にメソッドを生成すると面白そうですね。
optionsメソッドをそもそも受け付けないサーバーがあるからキツそうですけど・・・
require 'socket' class HttpClient attr_accessor :uri, :port, :path def initialize(uri, path, port) @uri = uri @path = path @port = port #@socket = TCPSocket.open(uri, port) end [:get, :post, :put, :delete, :head, :options].each do |method| define_method(method) do |content = nil, type = "text/plain"| @socket = TCPSocket.open(uri, port) #修正 : ソケットのopenを各メソッドに任せる。 @socket << "#{method.to_s.swapcase} #{self.path} HTTP/1.1\r\n" @socket << "Host: #{self.uri}\r\n" @socket << "Content-Type: #{type}; charset=utf-8\r\n" unless content.nil? @socket << "Content-Length: #{content.length}\r\n" @socket << "\r\n#{content}" end @socket << "Connection: close\r\n" @socket << "\r\n" puts @socket.read @socket.close end end end HttpClient.new("example.com", "/", 80).get HttpClient.new("example.com", "/", 80).post("こんにちは") HttpClient.new("example.com", "/", 80).options
各ヘッダーの意味
Host: #{ホスト名}
このホストにリクエスト投げるぞという意味です。Content-Type: #{タイプ}; charset=utf-8
文字コードとタイプの指定です。ここからはMIMEコード(メディアタイプ)の指定です。Content-Length:
その名の通りメッセージのサイズです。
最初Content-Lengthヘッダーを付けずにexample.comにpost投げつけたら以下のメッセージが返ってきました。
HTTP/1.0 411 Length Required Content-Type: text/html Content-Length: 357 Connection: close Date: Fri, 29 Apr 2016 09:52:50 GMT Server: ECSF (pae/3788)
Content-Length付けてリクエスト送信すると正常にレスポンスが返ってきました
HTTP/1.0 200 OK Accept-Ranges: bytes Cache-Control: max-age=604800 Content-Type: text/html Date: Fri, 29 Apr 2016 09:55:32 GMT Etag: "359670651" Expires: Fri, 06 May 2016 09:55:32 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: EOS (lax004/2816) Content-Length: 1270 Connection: close
405 Not Allowedが返ってくると思ったんですがちゃんとpostされてますね。空を切っている感。
HTTP/1.0とHTTP/1.1の違い
最初HTTP/1.1でレスポンスが返ってくるのが遅くて困っていました。
調べると以下のヘッダーが足りなかったみたいです。
Connection: close
Connectionヘッダーについて
HTTP/1.0とHTTP/1.1で変わった点に接続の継続性があります。これを表すのがConnectionヘッダーになります。具体的には、
Connection: keep-aliveにすると繋ぎっぱなしになり、 Connection: closeにするとTCPコネクションが閉じます。
HTTP/1.0では要求ごとに接続されていたのですが、HTTP/1.1ではコネクションを閉じない限り接続が続きます。つまりデフォルトでkeep-aliveになりました。
つまり、タイムアウトになるまでTCPコネクション繋ぎっぱなしにしていたことがレスポンスが遅かった原因です。
Connection: closeを書いていないリクエストのレスポンスにはConnection: closeが含まれていません。コネクションはサーバーアプリケーションが切ったようです。
HTTP/1.1 200 OK Allow: OPTIONS, GET, HEAD, POST Cache-Control: max-age=604800 Date: Fri, 29 Apr 2016 11:43:16 GMT Expires: Fri, 06 May 2016 11:43:16 GMT Server: EOS (lax004/280C) x-ec-custom-error: 1 Content-Length: 0
チャンクやキャッシュは次回にします。
参考
http://www.cresc.co.jp/tech/java/Servlet_Tutorial/Lesson_36.htm
rakeタスクを書く場所
この記事のjnchitoさんのコメントを見て確かに、と思いました。
rakeタスクのロジックはモデルに書くべき
何を当たり前の事をって感じですが、ビジネスロジックはモデルに書くべきです。
ですのでrake作りたいときはモデルにクラスメソッド書いてそのメソッドをrakeファイルから呼び出すようにすべきです。
モデルにメソッドを置くとテストも楽に出来るようになります。(上記事参照)
例えば、以下のような感じにします。
#lib/tasks/counter.rake namespace :count desc 'counter' task export: :environment do |t| Count.count_updadate end end #app/models/count.rb class Count def self.count_update end end
参考
サーバーでアクセス制御
apache
apacheでhttps://のみを使いたい、つまりhttp://にアクセスさせたくない時の設定です。
#.htaccess RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
最初の.*$は正規表現で、すべての文字列を表します。
%{HTTP_HOST}はアクセスがあったアドレスのドメイン部分、 %{REQUEST_URI}にはアクセスがあったアドレスのドメイン以下部分が入ります。
[R,L]は、それぞれRがリダイレクト、Lが変換終了を表します。
nginx
nginxでは/etc/nginx/nginx.confにアクセス制御文を書きます。
.htaccessをnginx.confに変換できるとっても素敵なサイトがあります。
htaccessファイルはnginxのに変換
お前ら言ってること分かるよな?って感じのサイトです。
参考
postfixでメールが送信されない
Apr 11 20:03:28 localhost postfix/smtp[12713]: 22E9C10C063F: to=<sc@gmail.com>, relay=gmail-smtp-in.l.google.com[2404:6800:4008:c01::1a]:25, delay=1, delays=0.12/0.01/0.37/0.54, dsn=5.7.1, status=bounced (host gmail-smtp-in.l.google.com[2404:6800:4008:c01::1a] said: 550-5.7.1 [2001:e42:103:2:153:121:91:20] Our system has detected that this 550-5.7.1 message does not meet IPv6 sending guidelines regarding PTR records 550-5.7.1 and authentication. Please review 550-5.7.1 https://support.google.com/mail/?p=ipv6_authentication_error for more 550 5.7.1 information. w9si2966434pfi.224 - gsmtp (in reply to end of DATA command))
設定を変更します。
// /etc/postfix/main.cf # Enable IPv4, and IPv6 if supported #inet_protocols = all inet_protocols = ipv4
postfixでめちゃくちゃハマりました。