package http_api

import (
    "crypto/tls"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "net/url"
    "strconv"
    "strings"
    "time"
)

type deadlinedConn struct {
    Timeout time.Duration
    net.Conn
}

func (c *deadlinedConn) Read(b []byte) (n int, err error) {
    return c.Conn.Read(b)
}

func (c *deadlinedConn) Write(b []byte) (n int, err error) {
    return c.Conn.Write(b)
}

// A custom http.Transport with support for deadline timeouts
func NewDeadlineTransport(connectTimeout time.Duration, requestTimeout time.Duration) *http.Transport {
    transport := &http.Transport{
        Dial: func(netw, addr string) (net.Conn, error) {
            c, err := net.DialTimeout(netw, addr, connectTimeout)
            if err != nil {
                return nil, err
            }
            return &deadlinedConn{connectTimeout, c}, nil
        },
        ResponseHeaderTimeout: requestTimeout,
    }
    return transport
}

type Client struct {
    c *http.Client
}

func NewClient(tlsConfig *tls.Config, connectTimeout time.Duration, requestTimeout time.Duration) *Client {
    transport := NewDeadlineTransport(connectTimeout, requestTimeout)
    transport.TLSClientConfig = tlsConfig
    return &Client{
        c: &http.Client{
            Transport: transport,
            Timeout:   requestTimeout,
        },
    }
}

// NegotiateV1 is a helper function to perform a v1 HTTP request
// and fallback to parsing the old backwards-compatible response format
// storing the result in the value pointed to by v.
//
// TODO: deprecated, remove in 1.0 (replace calls with GETV1)
func (c *Client) NegotiateV1(endpoint string, v interface{}) error {
retry:
    req, err := http.NewRequest("GET", endpoint, nil)
    if err != nil {
        return err
    }

    req.Header.Add("Accept", "application/vnd.nsq; version=1.0")

    resp, err := c.c.Do(req)
    if err != nil {
        return err
    }

    body, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
        return err
    }
    if resp.StatusCode != 200 {
        if resp.StatusCode == 403 && !strings.HasPrefix(endpoint, "https") {
            endpoint, err = httpsEndpoint(endpoint, body)
            if err != nil {
                return err
            }
            goto retry
        }
        return fmt.Errorf("got response %s %q", resp.Status, body)
    }

    if len(body) == 0 {
        body = []byte("{}")
    }

    // unwrap pre-1.0 api response
    if resp.Header.Get("X-NSQ-Content-Type") != "nsq; version=1.0" {
        var u struct {
            StatusCode int64           `json:"status_code"`
            Data       json.RawMessage `json:"data"`
        }
        err := json.Unmarshal(body, u)
        if err != nil {
            return err
        }
        if u.StatusCode != 200 {
            return fmt.Errorf("got 200 response, but api status code of %d", u.StatusCode)
        }
        body = u.Data
    }

    return json.Unmarshal(body, v)
}

// GETV1 is a helper function to perform a V1 HTTP request
// and parse our NSQ daemon's expected response format, with deadlines.
func (c *Client) GETV1(endpoint string, v interface{}) error {
retry:
    req, err := http.NewRequest("GET", endpoint, nil)
    if err != nil {
        return err
    }

    req.Header.Add("Accept", "application/vnd.nsq; version=1.0")

    resp, err := c.c.Do(req)
    if err != nil {
        return err
    }

    body, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
        return err
    }
    if resp.StatusCode != 200 {
        if resp.StatusCode == 403 && !strings.HasPrefix(endpoint, "https") {
            endpoint, err = httpsEndpoint(endpoint, body)
            if err != nil {
                return err
            }
            goto retry
        }
        return fmt.Errorf("got response %s %q", resp.Status, body)
    }
    err = json.Unmarshal(body, &v)
    if err != nil {
        return err
    }

    return nil
}

// PostV1 is a helper function to perform a V1 HTTP request
// and parse our NSQ daemon's expected response format, with deadlines.
func (c *Client) POSTV1(endpoint string) error {
retry:
    req, err := http.NewRequest("POST", endpoint, nil)
    if err != nil {
        return err
    }

    req.Header.Add("Accept", "application/vnd.nsq; version=1.0")

    resp, err := c.c.Do(req)
    if err != nil {
        return err
    }

    body, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
        return err
    }
    if resp.StatusCode != 200 {
        if resp.StatusCode == 403 && !strings.HasPrefix(endpoint, "https") {
            endpoint, err = httpsEndpoint(endpoint, body)
            if err != nil {
                return err
            }
            goto retry
        }
        return fmt.Errorf("got response %s %q", resp.Status, body)
    }

    return nil
}

func httpsEndpoint(endpoint string, body []byte) (string, error) {
    var forbiddenResp struct {
        HTTPSPort int `json:"https_port"`
    }
    err := json.Unmarshal(body, &forbiddenResp)
    if err != nil {
        return "", err
    }

    u, err := url.Parse(endpoint)
    if err != nil {
        return "", err
    }

    host, _, err := net.SplitHostPort(u.Host)
    if err != nil {
        return "", err
    }

    u.Scheme = "https"
    u.Host = net.JoinHostPort(host, strconv.Itoa(forbiddenResp.HTTPSPort))
    return u.String(), nil
}

api_request.go的更多相关文章

  1. 储物柜soket通信协议和中间件实现技术细节

    一.中间件程序的职责: 1)对柜机提供soket长连接的服务器端,就是soket server.可提供上万的客户端同时连接.用来实时响应控制请求,中间件必须随时知道某个柜机的在线状态,外部请求时才能判 ...

  2. 突破短板,传统桌面程序 使用webapi 扩展迎合web和移动端融合的需求

    传统桌面程序不能完全被web和移动端替代,但是需要改造.这里要说的是巧用webapi把以前用dll和com组件,ocx等方式做接口,做分布式开发的方式,改成restful 风格api的方式实现跨平台, ...

  3. PHP API 框架开发的学习

    基于互联网的应用正变得越来越普及,在这个过程中,有更多的站点将自身的资源开放给开发者来调用.对外提供的API 调用使得站点之间的内容关联性更强,同时这些开放的平台也为用户.开发者和中小网站带来了更大的 ...

  4. jQuery里面ajax请求的封装

    为了避免ajax漫天飞,我们需要对jQuery的代码进行封装,封装代码: function api_request(name, params, cb, scope, async, el) { if ( ...

  5. Python 基于request库的get,post,delete,封装

    # coding=utf-8 import json import requests class TestApi(object): """ /* @param: @ses ...

  6. 听说PHP的生成器yield处理大量数据杠杠的

    官方解释yield yield生成器是php5.5之后出现的,官方文档这样解释:yield提供了一种更容易的方法来实现简单的迭代对象,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大 ...

  7. 如何使用PHP的生成器yield处理大量数据业务

    官方解释yield yield生成器是php5.5之后出现的,官方文档这样解释:yield提供了一种更容易的方法来实现简单的迭代对象,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大 ...

  8. 20191011-构建我们公司自己的自动化接口测试框架-testrun最重要的模块

    testrun模块呢就是最终自动化测试入口,调用前面封装的各个模块主要流程是: 1. 获取测试集种待执行的测试用例 2. 处理测试用例获取的数据,包括转换数据格式,处理数据的中的关联等 3. 处理完数 ...

  9. 20191011-构建我们公司自己的自动化接口测试框架-Util的getTestSuite模块

    getTestSuite主要是用于在testData里面获取测试集以及对应的测试数据,包括2个主要的方法,一个是获取测试集,一个是获取测试集里面要执行的测试用例 获取测试集方法: from Util. ...

随机推荐

  1. 深入浅出理解python 装饰器

    之前就了解到了装饰器, 但是就会点皮毛, 而且对其调用方式感到迷茫,正好现在的项目我想优化,就想到了用装饰器, 因此深入研究了下装饰器.先看下代码: import time # 将函数作为参数传入到此 ...

  2. Java小技巧输出26个英文字母

    相信有的童鞋写到过与字母有关的小东西,是否有写过全部的字母呢?26个这么多字母,一个个打会疯掉.所有咱们可以用一个小技巧使用for循环帮我们把26个字母自动搞出来,大家来瞅一眼把! 使用Java遍历2 ...

  3. golang 常见疑惑总结

    经常会有一些朋友问go语言的一些问题和疑惑,其实好多问题在官方文档和stackoverflow里都有详细的讲解,只要你肯花时间读一遍官方文档和Effective Go基本上都有找到答案.本文总结一下大 ...

  4. Error filterStart的问题

    今天出现这个问题 严重: Error filterStart org.apache.catalina.core.StandardContext start 严重: Context startup fa ...

  5. 如何修改和查看tomcat内存大小

    为了解决tomcat在大进行大并发请求时,出现内存溢出的问题,请修改tomcat的内存大小,其中分为以下两种方式: 一.使用 catalina.bat 等命令行方式运行的 tomcat 查看系统最大支 ...

  6. 面试题-一个for循环输出一个棱形

    用一个for循环输出以下棱形 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ...

  7. easyui 在编辑状态下,动态修改其他列值。

    首先是自定义了一个方法uodateColumn更新列值 /** *自定义的修改列值方法 */ $.extend($.fn.datagrid.methods, { updateColumn: funct ...

  8. 解决miner.start() 返回null

    下面我列举了三种解决方案,前面两张是某两位大佬试出来的,第三种是我蒙出来的. 第一种解决方案 转载自  http://mp.weixin.qq.com/s/AB9vj1EzOgWT7JyryfQhDg ...

  9. FFMpeg编译之路

    为了编译这个东西,快折腾了一个星期了.期间经历了很多痛苦的过程,今天我把整个过程,以及在这个过程的感悟写下来,以备日后查看,也希望能帮到一些像我一样的兄弟姐妹. 在这一个星期里前前后后加起来总共使用了 ...

  10. OkHttp上传文件,服务器端请求解析找不到文件信息的问题

    长话短说,不深入解释了,官方给的上传案例代码: private static final String IMGUR_CLIENT_ID = "..."; private stati ...