challenge HTTPでGET その2

HTTPでGET その2

前回のお題
http://ja.doukaku.org/18/

HTTPで指定されたURLをGETするコードを書いてください。 
URLは「http://ja.doukaku.org/feeds/comments/」とします。 

ただし
・Proxyサーバを経由してGETしてください。
・タイムアウトを1秒に設定してください。
 (デフォルトが1秒でも、1秒に変更してください)
・タイムアウトを十分に小さくした場合、GETが失敗することを確認してください。

もしOSに依存する場合はそのOS名のタグを、 依存しない場合は「OS非依存」というタグをつけてください。
 わからなければつけなくても構いません。

Posted feedbacks - Flatten

Nested Hidden

Net::HTTP::Proxy を利用する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
require 'net/http'
def get_with_proxy(proxy_server, proxy_port)
    proxy = Net::HTTP::Proxy(proxy_server, proxy_port)
    http = proxy.new('ja.doukaku.org')
    http.open_timeout = 1
    http.start do |h|
      response = h.get('/feeds/comments/')
      puts response.body
    end
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/perl

use strict;
use warnings;

use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
$ua->timeout(1);
$ua->proxy('http', 'http://proxy.example.net:8001/');

my $res = $ua->get('http://ja.doukaku.org/feeds/comments');

if ($res->is_success) {
    print $res->content;
}
else {
    die $res->status_line;
}

特にproxy用のインタフェースはありませんが、http-getのserver引数にproxyサーバ、url引数に目的とする絶対URLを指定してやればproxyを通したアクセスになります。

タイムアウトはhttp-get自体ではまだサポートしていないので、スレッドを使ってみました。なのでpthreadサポートが必要ですが、pthreadが動けばOS非依存のはず。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(use rfc.http)
(use gauche.threads)

(define *proxy* "localhost")
(define *timeout* 1.0) ;seconds

(define (get/proxy-and-timeout url)
  (let1 t (make-thread (lambda () (receive r (http-get *proxy* url) r)))
    (or (thread-join! (thread-start! t) *timeout* #f)
        (begin (thread-terminate! t) #f))))

WebRequest.Timeout と WebRequest.Proxy から。意外と短く書けます。
OS 非依存。Mono とかでも動くのかな。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using System.IO;
using System.Net;
static class Program {
    static void Main() {
        WebRequest request = WebRequest.Create("http://ja.doukaku.org/feeds/comments/");
        request.Timeout = 1000;
        request.Proxy = new WebProxy("http://proxy.example.net:8001/");
        request.Proxy.Credentials = new NetworkCredential("username", "password");
        using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream())) {
            Console.WriteLine(sr.ReadToEnd());
        }
    }
}

1
2
3
4
5
from urllib2 import *
req = Request("http://ja.doukaku.org/feeds/comments/")
req.set_proxy("localhost:8000", "http")
socket.setdefaulttimeout(1.0)
print urlopen(req).read()

タイムアウトをこのデータ取得関数全体でのタイムアウトだと解釈して
すべてのメソッド呼び出し前にタイムアウトを設定しなおしています。

確認できませんがposix系でしか動かないかもしれません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import socket
from time import time

def geturl(url, proxy_host, proxy_port, timeout):
  timeout += time()
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(timeout-time())
  s.connect((proxy_host, proxy_port))
  s.settimeout(timeout-time())
  s.send('GET %s HTTP/1.1\r\n\r\n' % url)
  s.settimeout(timeout-time())
  r = d = s.recv(2048)
  while d:
    s.settimeout(timeout-time())
    d = s.recv(2048)
    r += d
  s.close()
  return r[r.index('\r\n\r\n')+4:]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.io._
import java.net._

val con = (new URL("http", "localhost", 8000, "http://ja.doukaku.org/feeds/comments/")).openConnection.asInstanceOf[HttpURLConnection]
con.setConnectTimeout(1000)
con.setReadTimeout(1000)
con.setRequestMethod("GET")
val read = (new BufferedReader(new InputStreamReader(con.getInputStream))).readLine _

def printContents:unit = read() match{
  case null =>()
  case s => println(s);printContents
}
printContents

WWW::Mechanizeで.

うちの環境では,timeout 1秒だと成功するorzので,0.1秒にしました.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/local/bin/ruby
require 'rubygems'
require 'mechanize'

class Crawler < WWW::Mechanize
  attr_accessor :agent

  PROXY_HOST = 'localhost'
  PROXY_PORT = '8080'
  URI = 'http://ja.doukaku.org/feeds/comments/'

  def initialize(conf = {})
    super()
    self.max_history = 1  # 幸せになるおまじない
    self.set_proxy(PROXY_HOST, PROXY_PORT)
    conf.each {|method, value| self.send(method, value) }
  end

  def fetch
    get(URI)
  end
end

# puts Crawler.new.fetch.body  # => succeed
puts Crawler.new({:open_timeout= => 0.1, :read_timeout= => 0.1}).fetch.body

う,ログインし忘れてた.


(http-get/proxy-&-timeout "http://ja.doukaku.org/feeds/comments/")
;時間内
; ==>http-requestの結果
;タイムアウト時
; ==> nil

Drakmaとタイムアウトの制御に、Portable-Threadsを使ってみました。
・Clozure CL 1.2 Linux/x86_64 
・SBCL 1.0.12 Linux/x86_64 
・Allegro 8.1 Express MacOSX/PPC
・CLISP 2.41 MacOSX/PPC
・LispWorks PE 5.0.1 MacOSX/PPC
で動作確認しています。
;; LispWorks限定ということならhttp-requestはタイムアウト制御の引数を取ることが
;; 可能なようです。
1
2
3
4
5
6
(require :portable-threads)
(require :drakma)

(defun http-get/proxy-&-timeout (uri &optional (expire 1) proxy port)
  (portable-threads:with-timeout (expire nil)
    (drakma:http-request uri :proxy (and proxy `(,proxy ,port)))))

ProxyサーバとTIMEOUTを設定してGETしています。
TIMEOUTを100msにした場合、java.net.SocketTimeoutException が発生します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.net.*;
import java.io.*;

public class Sample {
    public static final String SITE = "http://ja.doukaku.org/feeds/comments/";
    public static final String PROXY_HOST = "filter.nifty.com";
    public static final int PROXY_PORT = 8080;
    public static final int TIMEOUT = 1 * 1000; // m sec

    public static void main(String[] args) throws Exception {
        Proxy proxy = new Proxy(Proxy.Type.HTTP, 
                                new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        URLConnection uc = new URL(SITE).openConnection(proxy);
        uc.setConnectTimeout(TIMEOUT);
        uc.setReadTimeout(TIMEOUT);
        uc.connect();
        BufferedReader r = new BufferedReader(new InputStreamReader(
                uc.getInputStream(), "UTF-8"));
        String str;
        while ((str = r.readLine()) != null) {
            System.out.println(str);
        }
    }
}

おっと。失敗の確認に関して何も報告してなかった。
タイムアウト時には GetResponse が WebException を投げてきます。
自分の環境だと、1秒ではまず間違いなく例外が投げられますね。

HTTPパッケージにあるモジュール群を使う. タイムアウトは ghc-6.8.2 で標準になったSystem.TImeout モジュールを使う

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module Main (main) where

import Data.Maybe
import Network.Browser
import Network.HTTP
import Network.URI
import System.Environment
import System.IO
import System.Timeout
-- import qualified System.IO.UTF8 as U

main :: IO ()
main = do { prog <- getProgName
          ; args <- getArgs
          ; case args of
              [h] -> do { mr <- timeout tolerance $ httpRequest (mkreq h)
                        ; case mr of
                            Nothing -> hPutStrLn stderr $ prog ++ ": timeout"
                            Just (Right resp)
                              -> hPutStrLn stdout $ rspBody resp
                        }
              _   -> usage prog
          }
  where 
    mkreq h = Request uri GET [Header HdrHost h'] ""
      where 
         uri = fromJust $ parseURI h
         h'  = uriRegName $ fromJust $ uriAuthority uri
         
tolerance :: Int
tolerance = 10^6 -- micro sec
                            
usage :: String -> IO ()
usage prog = hPutStrLn stderr $ "usage: "++prog++" <uri>"

httpRequest :: Request -> IO (Result Response)
httpRequest req
 = do { proxy <- catch
                   (getEnv "HTTP_PROXY" >>= return . (flip Proxy Nothing))
                   (\_ -> return NoProxy)
      ; (_, resp) <- browse (setProxy proxy >> request req)
      ; return (Right resp)
      }

補足:プロキシは環境変数 HTTP_PROXY を見るようにしてあります。


Squeak Smalltalk で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
| sock url crlf list contents secs |
HTTPSocket useProxyServerNamed: 'proxy.example.com' port: 8080.
url := 'http://ja.doukaku.org/feeds/comments/' asUrl.
crlf := String crlf.
secs := HTTPSocket deadlineSecs: 1.
sock := HTTPSocket initHTTPSocket: url wait: secs ifError: [:err | ^err].
sock sendCommand: 'GET ', url fullPath, ' HTTP/1.1', crlf, crlf.
list := sock getResponseUpTo: crlf, crlf ignoring: String cr.
contents := (sock getRestOfBuffer: list third) contents.
sock destroy.
HTTPSocket stopUsingProxyServer.
^contents

Zend Frameworkで

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
require_once 'Zend/Http/Client.php';

$config = array(
    'adapter'    => 'Zend_Http_Client_Adapter_Proxy',
    'proxy_host' => 'ugnews.net',
    'timeout'=> 1
);

$client = new Zend_Http_Client('http://ja.doukaku.org/feeds/comments/', $config);
print_r($client->request('GET'));

Java / Scala とほぼ同じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
importPackage(java.net);
importPackage(java.io);

(function doukaku113(prox, timeout, port){ port || (port = 8080);
  prox = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(prox, port));
  with(new URL('http://ja.doukaku.org/feeds/comments/').openConnection(prox)) try{
    setConnectTimeout(timeout);
    setReadTimeout(timeout);
    connect();
    with(new BufferedReader(new InputStreamReader(getInputStream(), 'UTF-8')))
      for(var b; b = readLine();) print(b);
  } catch(e){ print(e) }
  return arguments.callee;
})(
  'localhost', 1000
  // => <?xml version="1.0" ...
)(
  'localhost', 100
  // => JavaException: java.net.SocketTimeoutException: Read timed out
);

libcurl を使ってみました。使い方は怪しいですが。
若干古いライブラリなので最近の D 2.0 コンパイラではダメだと思われます。
こちらでは dmd 1.025 を bud 経由で使いました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import std.stdio;
import libcurl;
import curldef;

void main(){
    auto curl = new LibCurl();
    ProxyData proxy;
    proxy.url = "proxy.example.net:8001";
    curl.proxy(proxy);
    curl.URL("http://ja.doukaku.org/feeds/comments/");
    curl.setOption(CURLOPT_TIMEOUT, 1);

    auto str = curl.downloadToString();
    writefln("%s", str);
}

#3704 を元にしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <string>

std::string content;

size_t writeMemoryCallback( char *ptr, size_t size, size_t nmemb  ){
   size_t realsize = size * nmemb;
   content.append(  static_cast<const char *>( ptr ), realsize );
   return realsize;
}

int main( int argc, char *argv[] ){

   try {
      cURLpp::Cleanup cleaner;
      cURLpp::Easy request;
      request.setOpt( new cURLpp::Options::Url( "http://ja.doukaku.org/feeds/comments/" ) );
      request.setOpt( new cURLpp::Options::WriteFunction(
                      cURLpp::Types::WriteFunctionFunctor( &writeMemoryCallback )));
      request.setOpt( new cURLpp::Options::Proxy( "example.com:80" ) );
      request.setOpt( new cURLpp::Options::Timeout( 1 ));
      request.perform();
   }
   catch ( cURLpp::LogicError & e ) {
      std::cerr << e.what() << std::endl;
      return 1;
   }
   catch ( cURLpp::RuntimeError & e ) {
      std::cerr << e.what() << std::endl;
      return 1;
   }
   std::cout << content << std::endl;
   return 0;
}

ざっくりと。

1
2
3
4
5
6
7
8
1> inets:start().
ok
2> http:set_options([{proxy,{{"pro.xy.ip.addr", 8080},[]}}]).
ok
3> http:request(get,{"http://ja.doukaku.org/feeds/comments/",[]},[{timeout,1000}],[]).
{error,timeout} ... Timeout1秒だと失敗
4> http:request(get,{"http://ja.doukaku.org/feeds/comments/",[]},[{timeout,10000}],[]).
{ok,{{"HTTP/1.0",200,"OK"}, ... (Timeout10秒だとOK)

groovyです。こちらにも解答してみます timeout=10でjava.net.SocketTimeoutExceptionがthrowされることを確認しました

1
2
3
4
5
6
7
8
proxyHost = "localhost"
proxyPort = 8000
url = "http://ja.doukaku.org/feeds/comments/"
timeout = 1000

a = new URL("http", proxyHost, proxyPort, url).openConnection()
a.setReadTimeout(timeout) 
println a.getInputStream().getText("UTF8")

Boost.Asioでゴリゴリ.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>

#include <cstring>
#include <exception>
#include <iostream>
#include <string>

boost::shared_ptr<boost::asio::ip::tcp::socket> g_socket;
boost::shared_ptr<boost::asio::deadline_timer> g_timer;
boost::array<char, 128> g_recv_buf;

void on_read(
    const boost::system::error_code& err,
    std::size_t bytes_transfered
    )
{
  if ( err ) {
    if ( err != boost::asio::error::eof )
      std::cerr << boost::system::system_error(err).what() << "\n";
    if ( g_timer )
      g_timer->cancel();
    return;
  }
  std::cout.write(g_recv_buf.data(), bytes_transfered);
  g_socket->async_read_some(boost::asio::buffer(g_recv_buf, g_recv_buf.size()), on_read);
}

void on_timeout(const boost::system::error_code& err)
{
  if ( err != boost::asio::error::operation_aborted ) {
    if (!err) {
      std::cerr << "TIMEOUT!\n";
      if ( g_socket )
        g_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    }
    else
      std::cerr << boost::system::system_error(err).what() << "\n";
  }
}

int main(int c, char** v)
{
  int timeout_sec = 1;

  if ( c < 2 ) {
    std::cout << "usage: " << v[0] << " <url> [<proxy url> [<timeour sec>]]\n";
    return 0;
  }
  const bool use_proxy = c > 2 && std::strlen(v[2]);
  const std::string proxy_url(use_proxy ? v[2] : "");
  const std::string target_url(v[1]);

  if ( c > 3 ) {
    try {
      timeout_sec = boost::lexical_cast<int>(v[3]);
    }
    catch (const std::exception& e) {
      std::cerr << "invalid timeout sec (" << v[3] << ") :" << e.what() << "\n";
      return 1;
    }
  }

  std::string proto, host, port, path;

  try {
    boost::regex r("(?:([^:]+)://)?([^:/]+)(?::(\\d+))?/?(.*$)");
    boost::smatch m;
    const std::string& url = use_proxy ? proxy_url : target_url;
    if ( boost::regex_match(url, m, r) ) {
      proto = m[1].length() > 0 ? m[1].str() : std::string("http");
      host  = m[2];
      port  = m[3];
      path  = use_proxy ? target_url : "/" + m[4];
    }
    else {
      std::cerr << "given url (" << url << ") doesn't match!\n";
      return 1;
    }
  }
  catch (const std::exception& e) {
    std::cerr << "error while analyzing url("
      << (use_proxy ? proxy_url : target_url) << ") : " << e.what() << "\n";
    return 1;
  }

  try {
    using boost::asio::ip::tcp;

    boost::asio::io_service io_service;
    
    tcp::resolver resolver(io_service);
    g_timer.reset(new boost::asio::deadline_timer(io_service));

    tcp::resolver::query query(host, port.empty() ? proto : port );

    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

    g_socket.reset(new tcp::socket(io_service));
    boost::system::error_code err = boost::asio::error::host_not_found;
    while (err && endpoint_iterator != end ) {
      g_socket->close();
      g_socket->connect(*endpoint_iterator++, err);
    }
    if ( err )
      throw boost::system::system_error(err);

    const std::string message("GET " + path + " HTTP/1.0\r\n\r\n");

    boost::asio::write(*g_socket, boost::asio::buffer(message),
        boost::asio::transfer_all(), err);
    if ( err )
      throw boost::system::system_error(err);


    g_timer->expires_from_now(boost::posix_time::seconds(timeout_sec));
    g_socket->async_read_some(boost::asio::buffer(g_recv_buf, g_recv_buf.size()), on_read);
    g_timer->async_wait(on_timeout);
    io_service.run();

    g_socket.reset();
    g_timer.reset();
  }
  catch (const std::exception& e) {
    std::cerr << e.what() << "\n";
    return 1;
  }

  return 0;
}

タイムアウトはtimeoutというオプション、プロキシサーバーはhttp_proxyという環境変数でそれぞれ指定します。

http_proxyは、起動後初回のダウンロード処理を実行する前に、一度だけ設定可能です。その後は設定可・不可の切り替えはできますが、プロキシサーバーのホストを変更することはできません(These environment variables must be set before the download code is first used: they cannot be altered later by calling Sys.setenv)。

タイムアウトを1秒にすると、大抵のGETは失敗してしまうみたいですね。

1
2
3
4
5
http.get <- function(url, proxy='', timeout.value=1){
  options(timeout=timeout.value)
  Sys.setenv(http_proxy=proxy)
  readLines(url)
}

libfetchがあれば動きます.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <fetch.h>
#include <unistd.h>

#define handle_error_fetch(str) do { \
    fprintf(stderr, "%s: %s\n", str, fetchLastErrString); \
    exit(EXIT_FAILURE); \
} while(0)

int
main(int argc, char **argv)
{
    const char *URL = "http://ja.doukaku.org/feeds/comments";
    const char * const PROXY_DEFAULT = "http://localhost:8080";
    FILE *fd;
    size_t len_r, len_w;
    u_char buf[8192], *buf_p;

    fetchTimeout = 1;   /* in seconds */
    setenv("HTTP_PROXY", PROXY_DEFAULT, 0);


    if ((fd = fetchGetURL(URL, NULL)) == NULL)
        handle_error_fetch("fetchGetURL");

    while (!feof(fd)) {
        len_r = fread(buf, 1, sizeof(buf), fd);
        for (buf_p = buf; (buf_p - buf) < len_r; buf_p += len_w)
            len_w = fwrite(buf_p, 1, len_r - (buf_p - buf), stdout);
    }
    fclose(fd);

    exit(EXIT_SUCCESS);
}

プロパティをいじる方法で、Sourceを使ってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import scala.io.Source

System.setProperty("http.proxyHost","proxy.server.jp")
System.setProperty("http.proxyPort","8080")

println(
  Source.fromURL("http://ja.doukaku.org/feeds/comments/")
        .getLines
        .mkString
       )

Index

Feed

Other

Link

Pathtraq

loading...