海运的博客

又一PHP libcurl封装异步并发HTTP客户端

发布时间:January 27, 2015 // 分类:PHP // No Comments

PHP标准库内置curl扩展,不过实现不完整,如multi_socket_action接口,无意中发现pecl http库同样基于libcurl封装,支持更多的libcurl特性,更新也比较快,底层通过libevent(epoll)实现multi_socket_action接口,不过pecl http版本1和版本2 api完全不兼容,使用过程中稳定性及性能并不如PHP内置的curl,好像还有内存泄露,以下为示例代码,基于pecl_http 2.20:

<?php
   function push($client, $url) {
      $req = new http\Client\Request("GET", $url, ["User-Agent"=>"My Client/0.1"]);
      $req->setOptions(array('connecttimeout'=>1, 'timeout'=>1));
      $client->enqueue($req, function($response) use ($client, $req, $url) {
         printf("%s returned '%s' (%d)\n", $response->getTransferInfo("effective_url"), $response->getInfo(), $response->getResponseCode());
         echo $client->count().PHP_EOL;
         global $urls;
         if ($urls) {
            while ($client->count() < 20) {
               $url = array_shift($urls);
               push($client, $url);
            }
            return true; // dequeue
         }
      });
   }

   $client = new http\Client;
   $client->enablePipelining(true);
   $client->enableEvents(true);

   for ($i = 0; $i < 10000; ++$i) {
      $urls[] = "http://192.168.1.3/";
   }
   for ($i = 0; $i < 20; ++$i) {
      $url = array_shift($urls);
      push($client, $url);
   }
   /*
   try{
      var_dump($client->send());
   }
   catch(http\Exception\RuntimeException  $e)
   {
      echo 'Message: ' .$e->getMessage().PHP_EOL;
   }
   */

   while ($client->once()) {
      $client->wait();
   }

Python CURL异步并发HTTP客户端

发布时间:January 7, 2015 // 分类:Python // No Comments

Select模式,类似于php multi curl异步并发,连接数不能太多:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import pycurl
import cStringIO

#最大连接数
num_conn = 20

queue = []
urls = ['https://www.haiyun.me/'] * 10000
for url in urls:
  queue.append(url)

num_urls = len(queue)
num_conn = min(num_conn, num_urls)
print ('----- Getting', num_urls, 'Max conn', num_conn,
       'connections -----')

m = pycurl.CurlMulti()
#初始化handle,可复用
m.handles = []
for i in range(num_conn):
  c = pycurl.Curl()
  c.body = cStringIO.StringIO()
  c.setopt(pycurl.FOLLOWLOCATION, 1)
  c.setopt(pycurl.MAXREDIRS, 5)
  c.setopt(pycurl.CONNECTTIMEOUT, 30)
  c.setopt(pycurl.TIMEOUT, 300)
  c.setopt(pycurl.NOSIGNAL, 1)
  m.handles.append(c)


freelist = m.handles[:]
num_processed = 0
#主循环开始
while num_processed < num_urls:

    #添加请求URL
    while queue and freelist:
      url = queue.pop()
      c = freelist.pop()
      c.setopt(pycurl.URL, url)
      c.setopt(pycurl.WRITEFUNCTION, c.body.write)
      m.add_handle(c)
      c.url = url
      #print url

    #执行请求
    while 1:
      (ret, num_handles) = m.perform()
      if ret != pycurl.E_CALL_MULTI_PERFORM:
        break

    #阻塞一会直到有连接完成
    m.select(1.0)

    #读取完成的连接
    while 1:
      (num_q, ok_list, err_list) = m.info_read()
      for c in ok_list:
        m.remove_handle(c)
        #print c.body.getvalue()
        freelist.append(c)

      for (c, errno, errmsg) in err_list:
        m.remove_handle(c)
        print ('Failed: ', c.url, errno, errmsg)
        freelist.append(c)
      num_processed = num_processed + len(ok_list) + len(err_list)
      if num_q == 0:
        break

for c in m.handles:
  c.fp = None
  c.close()
m.close()

epoll模式,php mult curl不支持此模式,tornado基于pycurl multi_socket_action封装的异步http client,每个client实例维护一个ioloop:

from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop
count = 10000
done = 0
def handle_request(response):
  global done
  done += 1
  if (done == count):
    #结束循环
    IOLoop.instance().stop()

  if response.error:
    print "Error:", response.error
  #else:
    #print response.body
#默认client是基于ioloop实现的,配置使用Pycurl
AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient",max_clients=20)
http_client = AsyncHTTPClient()
for i in range(count):
  http_client.fetch("https://www.haiyun.me/", handle_request)
#死循环
IOLoop.instance().start()      

基于epoll的multi curl在lan环境下效果不如select,因为所有Socket都在活跃状态,所有的callback都被唤醒,会导致资源的竞争。既然都是要处理所有的Socket,直接遍历是最简单最有效的方式.
为更好的性能建议libcurl/pycurl开启异步DNS解析

GO HTTP client客户端使用

发布时间:January 6, 2015 // 分类:GO // No Comments

简单:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"
)

func main() {
    res, err := http.Get("https://www.haiyun.me/")
    if err != nil {
        return
    }
        //执行close之前一定要判断错误,如没有body会崩溃
    defer res.Body.Close()
        //重用连接一定要执行上和下两步
    body, _ := ioutil.ReadAll(res.Body)
    fmt.Println(string(body))
        fmt.Println(res.Status)
    for k, v := range res.Header {
        fmt.Println(k, strings.Join(v, ""))
    }

}

自定义client,连接读写超时及自定义head:

package main

import (
        "fmt"
        "io/ioutil"
        "net/http"
        "strings"
        "time"
)

var timeout = time.Duration(20 * time.Second)

func dialTimeout(network, addr string) (net.Conn, error) {
        return net.DialTimeout(network, addr, timeout)
}

func main() {
        tr := &http.Transport{
                //使用带超时的连接函数
                Dial: dialTimeout,
                //建立连接后读超时
                ResponseHeaderTimeout: time.Second * 2,
                //直接通过ip访问时设置sni
                TLSClientConfig: &tls.Config{
                     ServerName: "www.haiyun.me",
                },
        }
        client := &http.Client{
                Transport: tr,
                //总超时,包含连接读写
                Timeout: timeout,
        }
        req, _ := http.NewRequest("GET", "https://www.haiyun.me", nil)
        req.Header.Set("Connection", "keep-alive")
        req.Header.Set("Host", "www.haiyun.me")
        res, err := client.Do(req)
        if err != nil {
                return
        }
        defer res.Body.Close()
        body, _ := ioutil.ReadAll(res.Body)
        fmt.Println(string(body))
        for k, v := range res.Header {
                fmt.Println(k, strings.Join(v, ""))
        }

}

使用代理及指定出口IP:

        //使用HTTP PROXY
        proxyUrl, err := url.Parse("http://host:port")
        tr := &http.Transport{
                Proxy: http.ProxyURL(proxyUrl),
        }
        //指定出口IP
        ief, err := net.InterfaceByName("eth0")
        addrs, err := ief.Addrs()
        addr := &net.TCPAddr{
                IP: addrs[0].(*net.IPNet).IP,
        }
        dia := net.Dialer{LocalAddr: addr}
        tr := &http.Transport{
                Dial: dia.Dial,
        }

使用socks5代理:https://github.com/hailiang/socks
http://golang.org/pkg/net/http/#Client
http://golang.org/pkg/net/http/#Transport
http://golang.org/pkg/net/#Dialer

ROS配置HotSpot热点Web认证服务

发布时间:October 19, 2012 // 分类:ROS // No Comments

HotSpot是一种通过Web网页认证访问网络的方法,用户可以使网页浏览器通过HTTP快速接入网络。
ROS开启HotSpot通过以下几个步骤:
1.配置用户IP地址池:

#https://www.haiyun.me
ip pool add name=hotspot ranges=192.168.1.2-192.168.1.254

2.添加认证用户组规则:

ip hotspot user profile add name=hotspot address-pool=hotspot shared-users=1 idle-timeout=00:10 rate-limit=128k/1024k

3.添加认证用户和密码:

ip hotspot user add name=hotspot profile=hotspot server=all user=user password=passwd

4.配置服务器规则:

ip hotspot profile add name=hotspot login-by=http-chap hotspot-address=192.168.1.1 

5.新增并开启HotSpot服务:

ip hotspot add name=hotspot profile=hotspot interface=br-lan disabled=no 

然后通过ROS访问外网浏览器会自动转向hotspot认证页面:
ros hotspot认证页面.png

Nginx判断accept_language禁止英文浏览器/操作系统访问

发布时间:June 27, 2012 // 分类:Nginx // No Comments

HTTP协议发送请求时会附加accept_language,通过它可知道浏览器所支持的语言,一般也为操作系统默认的语言。
列举一些accept_language标识:

zh 中文
zh-cn 大陆
zh-tw 台湾
zh-hk 香港
en 英文

配置Nginx允许中文用户访问,拒绝其它语言用户访问。

if ( $http_accept_language ~* ^[^zh])
{
return 404; #非中文用户访问网站返回404
}

Curl测试:

curl -I -H "Accept-Language:en"  www.haiyun.me
HTTP/1.1 404 Not Found
Date: Wed, 20 Jun 2012 18:38:33 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
分类
最新文章
最近回复
  • opnfense: 谢谢博主!!!解决问题了!!!我之前一直以为内置的odhcp6就是唯一管理ipv6的方式
  • liyk: 这个方法获取的IPv6大概20分钟之后就会失效,默认路由先消失,然后Global IPV6再消失
  • 海运: 不好意思,没有。
  • zongboa: 您好,請問一下有immortalwrt設定guest Wi-Fi的GUI教學嗎?感謝您。
  • 海运: 恩山有很多。
  • swsend: 大佬可以分享一下固件吗,谢谢。
  • Jimmy: 方法一 nghtp3步骤需要改成如下才能编译成功: git clone https://git...
  • 海运: 地址格式和udpxy一样,udpxy和msd_lite能用这个就能用。
  • 1: 怎么用 编译后的程序在家里路由器内任意一台设备上运行就可以吗?比如笔记本电脑 m参数是笔记本的...
  • 孤狼: ups_status_set: seems that UPS [BK650M2-CH] is ...