通通不可说

生命永远充满希望

0%

如何用Nginx反向代理openAI接口

最近国内很多地方都无法直接访问openAI的接口,从而无法接入ChatGPT,反向代理是一种解决方案。

反向代理是一种常见的服务器配置,通过它可以将客户端的请求转发给后端的服务。在这个教程中,我们将学习如何使用Nginx反向代理来访问OpenAI API。

步骤1:安装Nginx

首先,我们需要安装Nginx。在Ubuntu上,可以使用以下命令安装:

1
2
sudo apt-get update
sudo apt-get install nginx

如果有特殊的module需要添加(例如stream),可以自行编译安装,这里不做介绍。

在安装完成后,可以使用以下命令启动Nginx服务:

1
sudo systemctl start nginx

步骤2:配置Nginx

接下来,我们需要配置Nginx来反向代理OpenAI API。在Nginx的配置文件中添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name {your_domain_name};
location / {
proxy_pass https://api.openai.com/;
proxy_set_header Host api.openai.com;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

{your_domain_name}替换为您自己的域名。其中以下几项是为了保证使用stream参数请求时,EventSource类型响应的流畅输出。

1
2
3
4
5
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;

步骤3:测试反向代理

现在,我们可以测试反向代理是否正常工作。使用以下命令重新加载Nginx配置:

1
sudo systemctl reload nginx

然后,可以使用以下命令测试反向代理:

1
curl http://{your_domain_name}/v1/api/completions?prompt=Hello%2C%20my%20name%20is%20John%20and%20I%20am

如果一切正常,您应该能够收到来自OpenAI的响应。

如果nginx出现形如[error] 27648#27648: *49512 SSL_do_handshake() failed (SSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:SSL alert number 40) while SSL handshaking to upstream的错误日志,可以在代理配置添加如下配置:

1
proxy_ssl_server_name on;

完整的https配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 443 ssl;
server_name {your_domain_name};
ssl_certificate {your_cert_path};
ssl_certificate_key {your_cert_key_path};
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
location / {
proxy_pass https://api.openai.com/;
proxy_ssl_server_name on;
proxy_set_header Host api.openai.com;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

结论

在本教程中,我们学习了如何使用Nginx反向代理来访问OpenAI API。主要是保留EventSource类型响应的流畅输出功能,以提供良好的用户使用体验。

对python3 requests包的网络请求抓包时,https请求会报错,错误如下:

1
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

这时需要增加抓包工具用到的自签名证书,步骤如下:

  1. 终端执行命令python3 -m certifi,查看CA证书存放位置
  2. 将抓包工具生成的自签名证书内容(pem格式)复制到上面提示的文件中

Elasticsearch介绍

全文搜索属于最常见的需求,开源的 Elasticsearch 是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。

Elasticsearch 的底层是开源库 Lucene。Elasticsearch 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

原理简介参考终于有人把Elasticsearch原理讲透了!

  1. 基础概念

    1. 节点 Node、集群 Cluster 和分片 Shards

      ElasticSearch 是分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个实例。单个实例称为一个节点(node),一组节点构成一个集群(cluster)。

      分片是底层的工作单元,文档保存在分片内,分片又被分配到集群内的各个节点里,每个分片仅保存全部数据的一部分。

    2. 索引 Index、类型 Type 和文档 Document

      方便理解,对比我们比较熟悉的 MySQL 数据库:

      index → db

      type → table

      document → row

      这是一个不恰当的比喻。在数据库中,table之间是相互独立的,两个table中相同名字的column之间无关联。

      但在Elasticsearch中,Index下不同Type中Document的同名field是同一个,必须要有相同的映射定义。具体参考官方文档

  2. 使用 RESTful API 与 Elasticsearch 进行交互

    所有其他语言可以使用 RESTful API 通过默认端口 9200 和 Elasticsearch 进行通信。一个 Elasticsearch 请求和任何 HTTP 请求一样由若干相同的部件组成:

    curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

    被 < > 标记的部件:

    部件名作用

    VERB:适当的 HTTP 方法 或 谓词 : GET、 POST、 PUT、 HEAD 或者 DELETE。

    PROTOCOL:http 或者 https

    HOST:Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。

    PORT:运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。

    PATH:API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。

    QUERY_STRING:任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读)

    BODY:一个 JSON 格式的请求体 (如果请求需要的话)

    示例

    1
    2
    3
    4
    5
    6
    curl -XGET 'http://localhost:9200/_count?pretty' -d '
    {
    "query": {
    "match_all": {}
    }
    }'
  3. 文档管理(CRUD)

    1. 增加:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      POST /db/user/1
      {
      "username": "wmyskxz1",
      "password": "123456",
      "age": "22"
      }

      POST /db/user/2
      {
      "username": "wmyskxz2",
      "password": "123456",
      "age": "22"
      }

      这一段代码稍微解释一下,这其实就往索引为 db 类型为 user 的数据库中插入一条 id 为 1 的一条数据,这条数据其实就相当于一个拥有 username/password/age 三个属性的一个实体,就是 JSON 数据

      执行命令后,Elasticsearch 返回如下数据:

      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
      # POST /db/user/1
      {
      "_index": "db",
      "_type": "user",
      "_id": "1",
      "_version": 1,
      "result": "created",
      "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
      },
      "_seq_no": 2,
      "_primary_term": 1
      }

      # POST /db/user/2
      {
      "_index": "db",
      "_type": "user",
      "_id": "2",
      "_version": 1,
      "result": "created",
      "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
      },
      "_seq_no": 1,
      "_primary_term": 1
      }

      version 是版本号的意思,当我们执行操作会自动加 1

    2. 删除:

      1
      DELETE /db/user/1

      Elasticsearch 返回数据如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      {
      "_index": "db",
      "_type": "user",
      "_id": "1",
      "_version": 2,
      "result": "deleted",
      "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
      },
      "_seq_no": 1,
      "_primary_term": 1
      }

      这里就可以看到 version 变成了 2

    3. 修改:

      1
      2
      3
      4
      5
      6
      PUT /db/user/2
      {
      "username": "wmyskxz3",
      "password": "123456",
      "age": "22"
      }

      Elasticsearch 返回数据如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      {
      "_index": "db",
      "_type": "user",
      "_id": "2",
      "_version": 2,
      "result": "updated",
      "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
      },
      "_seq_no": 2,
      "_primary_term": 1
      }
    4. 查询:

      1
      GET /db/user/2

      返回数据如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      {
      "_index": "db",
      "_type": "user",
      "_id": "2",
      "_version": 2,
      "found": true,
      "_source": {
      "username": "wmyskxz3",
      "password": "123456",
      "age": "22"
      }
      }
  4. 搜索

    请求_search接口时,某些客户端不支持GET请求中有Body,需将GET改为POST。

    先往 Elasticsearch 中插入一些数据:

    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
    PUT /movies/movie/1
    {
    "title": "The Godfather",
    "director": "Francis Ford Coppola",
    "year": 1972,
    "genres": [
    "Crime",
    "Drama"
    ]
    }

    PUT /movies/movie/2
    {
    "title": "Lawrence of Arabia",
    "director": "David Lean",
    "year": 1962,
    "genres": [
    "Adventure",
    "Biography",
    "Drama"
    ]
    }

    PUT /movies/movie/3
    {
    "title": "To Kill a Mockingbird",
    "director": "Robert Mulligan",
    "year": 1962,
    "genres": [
    "Crime",
    "Drama",
    "Mystery"
    ]
    }

    PUT /movies/movie/4
    {
    "title": "Apocalypse Now",
    "director": "Francis Ford Coppola",
    "year": 1979,
    "genres": [
    "Drama",
    "War"
    ]
    }

    PUT /movies/movie/5
    {
    "title": "Kill Bill: Vol. 1",
    "director": "Quentin Tarantino",
    "year": 2003,
    "genres": [
    "Action",
    "Crime",
    "Thriller"
    ]
    }

    PUT /movies/movie/6
    {
    "title": "The Assassination of Jesse James by the Coward Robert Ford",
    "director": "Andrew Dominik",
    "year": 2007,
    "genres": [
    "Biography",
    "Crime",
    "Drama"
    ]
    }

    现在已经把一些电影信息放入了索引,可以通过搜索看看是否可找到它们。 为了使用 ElasticSearch 进行搜索,我们使用 _search 端点,可选择使用索引和类型。

    也就是说,按照以下模式向URL发出请求://_search。其中,index 和 type 都是可选的。

    换句话说,为了搜索电影,可以对以下任一URL进行POST请求:

    http://localhost:9200/_search - 搜索所有索引和所有类型。

    http://localhost:9200/movies/_search - 在电影索引中搜索所有类型

    http://localhost:9200/movies/movie/_search - 在电影索引中显式搜索电影类型的文档。

    搜索请求正文和ElasticSearch查询DSL

    如果只是发送一个请求到上面的URL,我们会得到所有的电影信息。为了创建更有用的搜索请求,还需要向请求正文中提供查询。

    请求正文是一个JSON对象,除了其它属性以外,它还要包含一个名称为 “query” 的属性,这就可使用ElasticSearch的查询DSL。

    1
    { "query": { //Query DSL here } }

    DSL是ElasticSearch自己基于JSON的域特定语言,可以在其中表达查询和过滤器。可以把它简单同SQL对应起来,相当于条件语句。

    基本自由文本搜索

    查询DSL具有一长列不同类型的查询可以使用。 对于“普通”自由文本搜索,最有可能想使用一个名称为“查询字符串查询”。

    查询字符串查询是一个高级查询,有很多不同的选项,ElasticSearch将解析和转换为更简单的查询树。如果忽略了所有的可选参数,并且只需要给它一个字符串用于搜索,它可以很容易使用。

    现在尝试在两部电影的标题中搜索有“kill”这个词的电影信息:

    1
    2
    GET /_search 
    { "query": { "query_string": { "query": "kill" } } }
    指定搜索的字段

    在前面的例子中,使用了一个非常简单的查询,一个只有一个属性 “query” 的查询字符串查询。 如前所述,查询字符串查询有一些可以指定设置,如果不使用,它将会使用默认的设置值。

    这样的设置称为“fields”,可用于指定要搜索的字段列表。如果不使用“fields”字段,ElasticSearch查询将默认自动生成的名为 “_all” 的特殊字段,来基于所有文档中的各个字段匹配搜索。

    为了做到这一点,修改以前的搜索请求正文,以便查询字符串查询有一个 fields 属性用来要搜索的字段数组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford",
    "fields": [
    "title"
    ]
    }
    }
    }
    条件匹配
    1
    2
    3
    4
    5
    6
    7
    8
    GET /_search
    {
    "query": {
    "match": {
    "year": 2007
    }
    }
    }
    过滤返回结果字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford",
    "fields": [
    "title"
    ]
    }
    },
    "_source": ["title","director"]
    }
    返回结果包含指定字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford",
    "fields": [
    "title"
    ]
    }
    },
    "_source": {
    "includes": [
    "title",
    "director"
    ]
    }
    }
    返回结果排除指定字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford",
    "fields": [
    "title"
    ]
    }
    },
    "_source": {
    "excludes": [
    "title",
    "director"
    ]
    }
    }
    排序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford"
    }
    },
    "sort": {
    "year": {
    "order": "desc"
    }
    }
    }
    分页
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    GET /_search
    {
    "query": {
    "query_string": {
    "query": "ford"
    }
    },
    "sort": {
    "year": {
    "order": "desc"
    }
    },
    "from": 0,
    "size": 1
    }
    多条件查询-布尔值查询
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GET /_search
    {
    "query": {
    "bool": {
    "must": [
    {
    "match": {
    "year": 2007
    }
    }
    ]
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GET /_search
    {
    "query": {
    "bool": {
    "should": [
    {
    "match": {
    "year": 2007
    }
    }
    ]
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GET /_search
    {
    "query": {
    "bool": {
    "must_not": [
    {
    "match": {
    "year": 2007
    }
    }
    ]
    }
    }
    }
    多条件查询-条件区间
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    GET /_search
    {
    "query": {
    "bool": {
    "must": [
    {
    "match": {
    "genres": "Drama"
    }
    }
    ],
    "filter": [
    {
    "range": {
    "year": {
    "gte": 2000
    }
    }
    }
    ]
    }
    }
    }
    精确查找

    term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇。

    match和term的区别是,match查询的时候,elasticsearch会根据给定的字段提供合适的分析器,而term查询不会有分析器分析的过程,match查询相当于模糊匹配,只包含其中一部分关键词就行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GET /_search
    {
    "query": {
    "bool": {
    "must": [
    {
    "term": {
    "year": 2007
    }
    }
    ]
    }
    }
    }
    短语搜索

    match_phrase 称为短语搜索,要求所有的分词必须同时出现在文档中,同时位置必须紧邻一致。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GET /_search
    {
    "query": {
    "bool": {
    "must": [
    {
    "match_phrase": {
    "director": "Andrew Dominik"
    }
    }
    ]
    }
    }
    }
  5. 聚合

    聚合分析简介

    1. ES聚合分析是什么?

    聚合分析是数据库中重要的功能特性,完成对一个查询的数据集中数据的聚合计算,如:找出某字段(或计算表达式的结果)的最大值、最小值,计算和、平均值等。ES作为搜索引擎兼数据库,同样提供了强大的聚合分析能力。

    对一个数据集求最大、最小、和、平均值等指标的聚合,在ES中称为指标聚合(metric)

    而关系型数据库中除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行指标聚合。在 ES 中group by 称为分桶桶聚合(bucketing)

    ES中还提供了矩阵聚合(matrix)、管道聚合(pipleline),但还在完善中。

    2. ES聚合分析查询的写法

    在查询请求体中以aggregations节点按如下语法定义聚合分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "aggregations" : {
    "<aggregation_name>" : { <!--聚合的名字 -->
    "<aggregation_type>" : { <!--聚合的类型 -->
    <aggregation_body> <!--聚合体:对哪些字段进行聚合 -->
    }
    [,"meta" : { [<meta_data_body>] } ]? <!--元 -->
    [,"aggregations" : { [<sub_aggregation>]+ } ]? <!--在聚合里面在定义子聚合 -->
    }
    [,"<aggregation_name_2>" : { ... } ]*<!--聚合的名字 -->
    }
    }

    aggregations 也可简写为 aggs

    3. 聚合分析的值来源

    聚合计算的值可以取字段的值,也可是脚本计算的结果

    指标聚合

    1. max min sum avg

    获取指定字段最大值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "max_year": {
    "max": {
    "field": "year"
    }
    }
    }
    }

    获取查询结果指定字段最小值,按指定字段排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    GET /_search
    {
    "size": 1,
    "query": {
    "match": {
    "genres": "Drama"
    }
    },
    "sort": [
    {
    "year": {
    "order": "asc"
    }
    }
    ],
    "aggs": {
    "max_year": {
    "min": {
    "field": "year"
    }
    }
    }
    }

    指定field,在脚本中用_value 取字段的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "size": 0,
    "aggs": {
    "sum_year": {
    "sum": {
    "field": "year",
    "script": {
    "source": "_value * 2"
    }
    }
    }
    }
    }
    2. 文档计数

    注意接口地址为_count

    1
    2
    3
    4
    5
    6
    GET /_count
    {
    "query": {
    "match_all": {}
    }
    }
    3. value_count 统计某字段有值的文档数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "year_count": {
    "value_count": {
    "field": "year"
    }
    }
    }
    }
    4. cardinality 值去重计数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "year_count": {
    "cardinality": {
    "field": "year"
    }
    }
    }
    }
    5. stats 统计 count max min avg sum 5个值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "stats": {
    "stats": {
    "field": "year"
    }
    }
    }
    }
    6. extended_stats 高级统计,比stats多4个统计结果: 平方和、方差、标准差、平均值加/减两个标准差的区间
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "stats": {
    "extended_stats": {
    "field": "year"
    }
    }
    }
    }
    7. percentiles 占比百分位对应的值统计
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "percentiles": {
    "percentiles": {
    "field": "year"
    }
    }
    }
    }
    8. percentile_ranks 统计值小于等于指定值的文档占比
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    GET /_search
    {
    "size": 0,
    "aggs": {
    "percentiles": {
    "percentile_ranks": {
    "field": "year",
    "values": [
    2000,
    1990
    ]
    }
    }
    }
    }

    桶聚合

    1. Terms Aggregation 根据字段值项分组聚合
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /_search
    {
    "size": 0,
    "aggs": {
    "year_terms": {
    "terms": {
    "field": "year"
    }
    }
    }
    }
    2. Filter Aggregation 对满足过滤查询的文档进行聚合计算
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    GET /_search
    {
    "size": 0,
    "aggs": {
    "filtered_year_terms": {
    "filter": {
    "match": {
    "genres": "Drama"
    }
    },
    "aggs": {
    "year_terms": {
    "terms": {
    "field": "year"
    }
    }
    }
    }
    }
    }

使用提示

  1. 新建文档索引若不指定字段类型映射,则默认用动态映射。string类型会映射成text。Elasticsearch中text与keyword的区别
  2. 新建文档索引时,如果有date字段,注意格式化结果和索引配置是否一致,若不一致新增索引文档会失败。
  3. es的服务端和客户端需要使用相同版本号,不通版本之间不兼容。

Elasticsearch Head插件

elasticsearch-head将是一款专门针对于elasticsearch的客户端工具,提供Chrome浏览器插件。仓库地址https://github.com/mobz/elasticsearch-head

Choose Runtime
CodeGlance
GitToolBox
Grep Console
Key Promoter X
Maven Helper
Rainbow Brackets
RestfulToolkit
String Manipulation

Trojan简介

trojan是近两年兴起的网络工具,项目官网https://github.com/trojan-gfw ,文档官网是 https://trojan-gfw.github.io/trojan 。与强调加密和混淆的SS/SSR等工具不同,trojan将通信流量伪装成互联网上最常见的https流量,从而有效防止流量被检测和干扰。

安装Trojan

终端输入如下命令:

1
sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/trojan-gfw/trojan-quickstart/master/trojan-quickstart.sh)"

该命令会下载最新版的trojan并安装。安装完毕后,trojan配置文件路径是 /usr/local/etc/trojan/config.json

重点关注以下属性:

  1. local_port:监听的端口,默认是443,下文中因为要将Trojan放在Nginx后,因此改为了别的端口;
  2. remote_addrremote_port:非trojan协议时,将请求转发处理的地址和端口。可以是任意有效的ip/域名和端口号,默认是本机和80端口。一般是伪装的web站点服务;
  3. password:密码。需要几个密码就填几行,最后一行结尾不能有逗号;
  4. certkey:域名的证书和密钥;
  5. key_password:默认没有密码(如果证书文件有密码就要填上)

根据自己的需求修改配置文件,保存,然后设置开机启动:systemctl enable trojan,并启动trojan: systemctl start trojan

配合nginx

Trojan 默认端口为443,接管了所有443端口的流量,其他请求则是通过Trojan转发到了其他服务,例如nginx。如果有需求是将Trojan放在nginx后面,由nginx统一接收流量再分发到Trojan和其他服务(web,v2ray),可以参考下面的配置。

要实现上面的需求,用到了nginx的stream_ssl_preread_module模块,具体资料可以参考 http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html
编译nginx时增加此模块。

1.将Trojan配置文件的local_port改为非443端口,例如9443。将nginx原有443端口的配置改为非443端口,例如8443
2.nginx.conf中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
stream {
map $ssl_preread_server_name $name {
your.trojan.domain.address trojan;
default nginx;
}
upstream trojan {
server 127.0.0.1:9443;
}
upstream nginx {
server 127.0.0.1:8443;
}
server {
listen 443;
listen [::]:443;
proxy_pass $name;
ssl_preread on;
}
}

执行nginx -t测试配置文件,若提示nginx: [emerg] unknown directive "stream" in XXX,则在nginx.conf中的events区块前增加下面的语句

1
load_module /usr/lib/nginx/modules/ngx_stream_module.so;

具体原因参考 https://serverfault.com/questions/858067/unknown-directive-stream-in-etc-nginx-nginx-conf86

以上就是Trojan放在Nginx后面的配置流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env bash
ARGS=($*)
ADB="adb"
ANDROID_DEVICES=(`adb devices | sed '1d' | awk '{print $1}'`)
if [[ ${#ANDROID_DEVICES[@]} -gt 1 ]];then
for((j=0;j&lt;${#ANDROID_DEVICES[@]};j++))
do
echo $(expr ${j} + 1).${ANDROID_DEVICES[$j]}
done
while [[ $REPLY == "" || $REPLY == "0" ]];do
read -p "输入序号选择设备:"
done
ADB="adb -s "${ANDROID_DEVICES[$REPLY-1]}
fi
${ADB} ${ARGS[@]}

db.system.profile.find({"op" : "query","ns" : "poetries", millis : { $gt : 100 },ts : { $gt : new ISODate("2018-06-07T00:00:00.000Z"),$lt : new ISODate("2018-06-07T10:00:00.000Z")}}).pretty()


db.getProfilingLevel()   1为开启

为了方便填写git commit message,写了一个脚本用于读取jira上的issues,并列出选择作为commit message。 用到了git hook中的prepare-commit-msg 下面提供bash shell和python3两个版本 1.bash shell 此版本需要安装jq。

#!/bin/bash
USERNAME=`git config --get user.name`
if [ "$2" = "message" -o "$2" = "commit" ]
then
    exit 0
fi
exec 2> /dev/null
ISSUS=`curl -u jira:jira "http://localhost:8081/rest/api/2/search?jql=status+in+(Reopened,%20%22To%20Do%22,%20%22In%20Progress%22)%20AND%20assignee%20in%20($USERNAME)%20ORDER%20BY%20updated%20DESC" | jq '.issues[]|{summary: .fields.summary, key: .key}'`
KEYS=(`echo $ISSUS | jq '.key'`)
SUMMARYS_TMP=("`echo $ISSUS | jq '.summary'`")
IFS_old=$IFS
IFS=$'\n'
SUMMARYS=($SUMMARYS_TMP)
IFS='"'
number=${#KEYS[@]}
declare -a ISSUSES='()'
for((i=0;i<$number;i++))
do
    ISSUSES[$i]="${KEYS[$i]}${SUMMARYS[$i]}"
done
for((j=0;j<${#ISSUSES[@]};j++))
do
    let k=$j+1
    echo $k.${ISSUSES[$j]}
done
exec < /dev/tty
exec 2>&1
read -p "请输入编号:"
let answer=$REPLY-1
echo ${ISSUSES[$answer]} > $1
IFS=$IFS_old

2.python3

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, os, re
from subprocess import check_output
import requests
import json

def query_report(user):
    s = requests.Session()
    s.post('http://localhost:8081/rest/auth/1/session', json={"username":"jira","password":"jira"})
    url = 'http://localhost:8081/rest/api/2/search?jql=status+in+(Reopened,%20%22To%20Do%22,%20%22In%20Progress%22)%20AND%20assignee%20in%20(' + user +')%20ORDER%20BY%20updated%20DESC'
    reports = s.get(url)
    return reports.json()

sys.stdin = open('/dev/tty')
# 收集参数
commit_msg_filepath = sys.argv[1]
if len(sys.argv) > 2:
    commit_type = sys.argv[2]
else:
    commit_type = ''
if len(sys.argv) > 3:
    commit_hash = sys.argv[3]
else:
    commit_hash = ''

if commit_type == "message":
    sys.exit(0)
# 检测我们所在的分支
branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip()
branch = str(branch, 'utf-8')
print("On branch '%s'" % branch)
username = check_output(['git', 'config', '--get', 'user.name']).strip()
username = str(username, 'utf-8')
messages = []
if branch.startswith('master'):
    j = query_report(username)
    if j["total"] > 0:
        issues = j["issues"]
        for i in range(len(issues)):
            print(str(i+1) + ':' + issues[i]["key"] + " " + issues[i]["fields"]["summary"])
            messages.append(issues[i]["key"] + " " + issues[i]["fields"]["summary"]);
    number = input("请输入编号: ")
    message = messages[int(number) - 1]
    with open(commit_msg_filepath, 'w') as f:
        f.seek(0, 0)
        f.write(message)

1.先安装编译环境需要的软件

sudo apt-get install openjdk-7-jdk git-core gnupg flex bison gperf build-essential \

zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \

lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \

libgl1-mesa-dev libxml2-utils xsltproc unzip

2.同步代码

#新建目录
mkdir workspace
cd workspace
#链接为清华镜像,--repo-url是repo工具的地址,--no-repo-verify关闭repo工具验证
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-6.0.1_r79 --repo-url=https://mirrors.tuna.tsinghua.edu.cn/git/git-repo --no-repo-verify
#-d用清单文件里的版本;-c当前分支;--no-tags不拉取tag
repo sync -d -c --no-tags

3.开始编译

#初始化环境
source build/envsetup.sh
#选择产品
lunch
#用16线程编译
make -j16

有一个不在.gitmodule文件中的submodule在更新,解决办法就是

git ls-files --stage | grep 160000

这可以看到所有的Submodule文件,然后

git rm --cached PATH