记一次正向代理使用

背景

最近项目在接一家支付机构,支付机构的接口调用有ip的白名单限制,因此在这里我遇到了几个头疼的问题

  • 我司的网络出口ip不固定 导致ip白名单无法生效
  • 支付机构的调用地址是https 因此无法直接使用nginx来正向代理

解决过程

为了解决这个问题,通过网络上获取的知识了解到,nginx的http_proxy_connect模块可以转发https请求
因此只需要通过编译nginx的模块即可

  1. 找到http_proxy_connect的patch包 https://github.com/chobits/ngx_http_proxy_connect_module#select-patch

  2. 根据文档进行编译 (openresty也是这样编译的)
    在nginx目录下 首先克隆项目 然后执行如下命令即可

    $ patch -p1 < ./ngx_http_proxy_connect_module/patch/proxy_connect.patch
    $ ./configure --add-dynamic-module=./ngx_http_proxy_connect_module
    $ make && make install
  3. 在nginx.conf配置中增加一行(尽可能在第一行增加 后面增加会抛时机不对的错误)

    load_module   /usr/local/nginx/modules/ngx_http_proxy_connect_module.so;
  4. 增加代理服务器配置
    新增forward.conf文件 内容如下

    server {
     listen  9468;
     # dns resolver used by forward proxying
     resolver 8.8.8.8;
     # forward proxy for CONNECT request
     proxy_connect;
     proxy_connect_allow            443 563;
     proxy_connect_connect_timeout  10s;
     proxy_connect_read_timeout     10s;
     proxy_connect_send_timeout     10s;
    
     # forward proxy for non-CONNECT request
     location / {
         proxy_pass http://$host;
         proxy_set_header Host $host;
     }
    }
  5. 宿主机命令使用
    x.x.x.x需要换成nginx所在的机器

    export  http_proxy=x.x.x.x:9468
    export  https_proxy=x.x.x.x:9468
  6. postman中使用
    在设置中找到proxy 添加Add a custom proxy configuration即可

  7. 宿主机全局代理

ubuntu系统可以在设置中主动设置网络代理开启

记一次反向代理下载异常的问题

最近接手了一个java项目
偶然一个时刻 后台的导出功能突然失效了

经过查看日志定位到异常
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer

同时排查出该功能在本地是正常的
于是联想到正式环境使用的是nginx的反向代理
检查nginx配置 发现确实没有配置proxy_buffering

然后在反向代理的配置项中 增加配置

 proxy_buffering off;

最后问题得到了解决

一般来说,下载功能不推荐直接输出流数据,一般采用生成文件后,上传到一个文件服务器,然后返回文件服务器地址提供给客户端进行下载,引以为戒!

构建php镜像(docker)

背景

最近着手进行公司内项目的容器化改造,因此需要构建一个php的docker镜像

说实话,php由于本身的原因,在安装扩展方面确实没有其他语言那么顺畅,注定是要采坑的
经历了各种各样的错误信息以后,最终是构建完成了

构建的版本

  • php8.0-fpm
  • alpine

ps: 构建镜像的时候,我们应该尽可能的选择alpine系统,来保证镜像的大小,因为在实际的运行过程中,我们会有成千上万个contiainer在运行,镜像每多100M的容量,在运行过程中的消耗都是不可估量的

这里给出一份Dockerfile

FROM php:8.0-fpm-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk update
RUN apk add --no-cache  freetype-dev \
        libjpeg-turbo-dev \
        libmcrypt-dev \
        libpng-dev \
        zlib-dev

RUN apk add gcc g++ make libffi-dev openssl-dev m4 autoconf

RUN docker-php-ext-install pdo_mysql pcntl opcache bcmath \
    && docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

RUN pecl install  grpc \
     && docker-php-ext-enable grpc  
RUN printf "no\nno\nno\n" | pecl install -o -f redis \
    && docker-php-ext-enable redis \
    && rm -rf /tmp/pear

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
        && composer config -g repo.packagist composer https://packagist.phpcomposer.com 

这期间遇到了挺多问题的 稍微列举一下吧

  1. ERROR: `phpize' failed

    原因是没有安装m4 autoconf 使用命令apk add m4 autoconf 解决

  2. configure: error: C compiler cannot create executables

    原因是用的镜像是alpine版的 很多的工具和依赖在原始的系统内并不存在
    使用命令apk add gcc g++ make libffi-dev openssl-dev解决

踩一次vagrant网络的坑

踩一次vagrant网络的坑

问题描述
使用vagrant启用虚拟机后,局域网内可以访问,但是局域网的无线wifi无法访问

环境描述

主机:

  • ip: 192.168.1.189
  • netmask: 255.255.255.0
  • gateway: 192.168.1.254

虚拟机

  • ip: 192.168.1.188
  • netmask: 255.255.255.0
  • gateway: 此处有坑 下文详细介绍

验证过程

最开始发现这个问题的时候,就已经猜测是网络的问题了
于是我启动另一台windows虚拟机,使用手动配置ip的方式 发现网络是可达的
因此这个地方就可以确定是网关配置的问题了
那么翻一下vagrant的官方文档
找到了这样一个配置

Vagrant.configure("2") do |config|
  config.vm.network "public_network", ip: "192.168.0.17"

  # default router
  config.vm.provision "shell",
    run: "always",
    inline: "route add default gw 192.168.0.1"

  # default router ipv6
  config.vm.provision "shell",
    run: "always",
    inline: "route -A inet6 add default gw fc00::1 eth1"

  # delete default gw on eth0
  config.vm.provision "shell",
    run: "always",
    inline: "eval `route -n | awk '{ if ($8 ==\"eth0\" && $2 != \"0.0.0.0\") print \"route del default gw \" $2; }'`"
end

仔细看看这里有一个这样的配置

inline: "route add default gw 192.168.0.1"

那么解决方法就来了

解决问题

首先在虚拟机中ifconfig查看自己的网卡
我的ubuntu中是enp0s8 (centos中一般是eth1)

修改VagrantFIle如下

 config.vm.network "public_network", ip:"192.168.110.191", bridge:"enp4s0"
 config.vm.provision "shell", run: "always", inline: "route add default gw 192.168.110.254  enp0s8"

最后执行命令,搞定

vagrnat provision

随记

我一直在反思一件事情,我们现在的开发模式真的能够适应我们现在的环境么?

我们真的能仅仅以做一个项目的角度来考虑我们现在做的事情么?

出现一个需求 前后端排期 分一下工 做完这个需求 真的就结束了这一次任务么

隐藏在这个任务更加深层的逻辑 究竟要由谁 在什么时间点来完成

有没有这样一个角色担当 来为整个项目团队负责

也许在大多数人看来 做业务才是有价值的吧

包括我自己也是一样

很多时候都会被表面的短期价值所蒙蔽

总任务我今天写的两行代码 明天修复了两个bug 就能产生收益

但这其实就是一个谬论吧

只有取到成果以后才有论功行赏的过程

因此我们应该坚定一件事情

只要我们的出发点不是仅基于自身的利益为出发点的

那么这些行动就存在它的价值

不计较某一行代码

某一个功能的得失

未来的路很长

也许我们现在的行动 并不一定能最终都产生积极的意义

但是只要方向正确

我们总有抵达彼岸的那天

shell中子shell的妙用

shell脚本子shell的妙用

目录结构

├── a
├── b
├── c
├── d
└── test.sh

现有这样一段脚本(此处代码加上行号是为了方便说明)

 1 #!/bin/bash
  2 
  3 set -e
  4 
  5 pwd=`pwd`
  6 
  7 for file in  `ls $pwd`
  8 do
  9    if [ -d $file ]
 10    then
 11        cd $pwd/$file
 12        echo $file
 13    fi
 14 done

执行该脚本后,得出的结果是

a

这里很有意思的地方是,for循环仅仅执行了一次就退出了

内建的cd 命令切换了目录以后 居然会导致当前的循环中断

为了避免这种情况的发生,我们可以借鉴一下子shell的概念 把cd 命令当成子shell作为独立的进程来运行 这样就不会对当前的脚本产生影响了

# 子shell的格式是用括号把一系列的命令包裹起来
(
    command1
    command2
    command3
)

因此经过一番修改,我得到了下面的脚本(在11和14行代码处加上了括号)

  1 #!/bin/bash
  2 
  3 set -e
  4 
  5 pwd=`pwd`
  6 
  7 for file in  `ls $pwd`
  8 do
  9    if [ -d $file ]
 10    then
 11     (
 12        cd $pwd/$file
 13        echo $file
 14     )
 15    fi
 16 done

这样就能得出正常的结果了

a
b
c
d

我理解的优秀

如何定义一个候选人是否优秀? 这个问题在我这段时间参与面试这么长时间来,始终萦绕在我的脑海里。

在如此艰难的生活压力之下,或许对于大多数人来说,工作仅仅是一种获得收入来源的方式而已。但是我想说的是,不可以没有梦想,因为有趣的灵魂往往就体现在此处。

工作绝对是一个双向的选择,企业需要员工来产生效益,员工需要平台来展现自我价值。如果在每一次求职过程中,只是在盲目的寻找,抱着一副无所谓的态度,那么一定需要摆正自己的态度了。人以类聚物以群分,抱着无所谓的态度最后唯一的选择一定也是一个没有任何发展潜力的企业,这样的组合是没有未来的。

我非常喜欢一句话:"一个连企业文化都没有的企业,一定是没有办法活过今天晚上的"。

在人生历程中,不管你是否选择创业还是选择一份工作,那么首先你自己一定要变的优秀,然后去接触更多优秀的伙伴,只有这样才会有一个优秀的未来。

那么如何定义一个人是否优秀呢? 也就是怎样去定义一个人的价值?

优秀的人一生都在开挂,所以要想办法让自己的变得优秀

自我认知

如果连自己的无法认清,那么该怎样去认清这个世界?

说实话该如何认清自己,这个答案其实很难进行判断,每个人都会有自己的定义,但是从我的角度进行解析,我觉得可以做到这些事情

明确自己的喜恶

这一点或许可以叫爱憎分明吧,在孩童时代我们我们总是盲目的接受这个世界的知识,对于任何事情都是言听计从,但是随着年龄的增长以后,你就会有自己喜欢和不喜欢的东西,所以明确他们。对于自己不喜欢的人或事,勇敢一点,大声的say no。

确定人生的目标

没有目标的人生都是在虚度自己的光阴,给自己做一个人生的规划吧,短期规划或者长期规划都可以,前进的方向必须要有,哪怕是一个错误的方向也一定比没有方向来的要好。

展现个人特点

为什么是个人特点,而不是自己的优缺点呢?因为在我的定义中,优点和缺点都不是绝对的,所以每当有人问我有什么优点或者缺点的时候,我的回答都是同一个: "我略带一些完美主义倾向和强迫症"。

没错,优点和缺点是一样的,因为我不觉得自己有什么优点,当然也不会有什么缺点,那些都是我个人固有的人格特点。这个世界上,能够承担你本人的责任的只有你自己,因此你的优缺点应该由你自己来定义,没有任何人可以对你的优点和缺点下定义。因此,我把这个地方叫做个人特点,一定要勇敢一些,贯彻自己的特点,不要人云亦云。或许你觉得自己没有什么特点,但是不要着急,它一定会出现的。

思维方式

自我认知是对自己的认知, 那么思维方式就是用来认识外界的手段了,试着尝试以不同的角度去理解这个世界,你会有不一样的收获。

换位思考

说起来是很简单,但是做起来真的好难,对于没有经历过的事情去凭空想象,那是需要极大的想象力的天才才能做到。虽然只有真正处在对方的那个位置,才能明白对方在想什么,但是去尝试想象一下,或许有意外收获呢

以求职的过程来做个说明,假设你是一名求职者,切换一下自己的位置,你会录用你自己么?

非常简单浅显的一个道理,如果你自己都不敢相信自己,如何能指望他人来相信你呢。 借用编程界的一句经典话语: “建立在不可靠服务的基础上构建的服务,都是不可靠的(TCP协议除外)

墨守成规 or 勇于创新

思维方式没有对错之分,但是你要有自己的思维方式。没错,正如上面所说的个人特点和人生目标一样,你必须要有自己的思维方式。

自我驱动

潜力代表你的过去,能力代表你的现在,只有学习才能创造决定自己的未来

自主学习能力

主动学习和被动学习带来的效果一定是不一样的,在任何情况,去主动的学习你想要知道的知识

虽然在我们国家,大多数的人总是跟随着教师学习一些书本上的知识,但是这一点让我深深的感受到了悲伤

我多么的期望我的每一个老师能够教会我的不仅仅是知识,而是学习.学会学习一定比学会知识要来的重要.

虽然我明白这件事情经过了很长的时间,但是任何时候你要你明白了,那就永远都不晚

时间管理

没有目标的人生是虚度,那么没有管理到的时间那就是浪费了。有了时间管理才有挽救你的拖延症。

有时间一定要多看看时间管理之类的书脊,养成良好的规划,分清事情的轻重缓急,多做你觉得重要的事情。

性格品德

性格品德就没有什么好说的了,生活在天朝这个美好的国度,请认同这个最大的核心价值观,这是作为一个中国人最基本的骄傲。

富强、民主、文明、和谐,自由、平等、公正、法治,爱国、敬业、诚信、友善

解决dia在ubuntu下无法输入注释的问题

系统环境介绍

  • 操作系统: ubuntu16.0.4
  • dia版本: 0.97.3

遇到的问题

在图表 数据库以及uml图中 对应的字段注释无法填写

解决方案

/usr/bin/dia 文件中的启动环境变量修改一下


# 打开dia启动文件 sudo vim /usr/bin/dia # 把 dia-normal --integrated "$@" 修改为下面这种形式 LIBOVERLAY_SCROLLBAR=0 dia-normal --integrated "$@"

laravel源码浅析

  • laravel版本5.8

入口

当你想要弄清楚任何一件事物的内部构造时,一定需要先找到入口,因此第一件要做的事情就是找到入口文件 public/index.php

<?php
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

精简一下,只有9行代码,非常的简洁,包括<?php在内只有10行代码

  1. 定义常量 表示laravel运行的开始时间
  2. 包含composer的自动加载 曾经有psr0 和psr4规范 但是在我写下这个文档的时间(2019-06-04)已经废弃psr0很久了
  3. 加载laravel引导 (把laravel想象成一个操作系统 此处的引导就是操作系统的那512M字节的数据),并得到laravel的应用变量
  4. 使用$app变量,制作一个Http内核$kernel (没错 就是操作系统的那个内核)
  5. 使用内核($kernel)处理 $request 并得到一个$response
  6. 发送$response
  7. $kernel内核终止请求与响应

至此一次完整的laravel的生命周期就结束了,同时思考一下http协议,一个完整的HTTP事务应该是这个样子

客户端发送请求->服务器处理请求并响应->客户端收到响应

对于一个http请求来说,只有请求和响应两个过程,而laravel的入口恰好完美的诠释了这个神圣而又复杂的过程.

写代码虽然很简单,但是能把代码写的像散文一样一定是非常困难的,而这个入口文件就让人心旷心神

引导

$app = require_once DIR.'/../bootstrap/app.php';

<?php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

大多数的其他框架都倾向于把引导放在入口里,但是laravel是单独放了一个文件,并且这个文件也非常的简洁

以代码量计算只有17行,以功能计算只有5个

  1. 根据路径new出一个Application类
  2. 绑定一个Http内核单例
  3. 绑定一个Console内核单例
  4. 绑定一个异常处理类单例
  5. 返回$app实例

不需要知道Application内部做了什么 也不需要知道singleton内部做了什么,仅仅只阅读这几行代码,就能窥豹一斑

laravel的app实例仅仅只有三个功能

  • http请求处理 由http内核提供
  • 控制台命令处理 由Console内核提供
  • app异常处理 由Exceptions\Handler提供

最后返回一个app实例,这个app实例是继承于Container类的,这里运用反射技术来提供了一个根据类名生成类实例的功能,具体实现可以查看laravel/framework/src/illuminate/Container/Container.php的第790行代码

Container容器的具体用法在后面的篇章中会详细的进行解释,此处不多做介绍

http 内核

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

使用$app实例制作一个实现了Http/Kernel契约的内核实例

还记得引导文件中的这一行代码么

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

没错,这个地方返回的内核就是绑定的App\Http\Kernel::class 实例,并且仅初始化了基本的中间件而已

这里make的是一个imterface,因此只要你的Kernel实现了Illuminate\Contracts\Http\Kernel::class这个接口,那么在上面的文件中你完全可以自定义属于自己的内核,虽然没有什么必要

处理请求

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

先看这一行代码 ,捕获http请求

$request = Illuminate\Http\Request::capture()

暂时不深入剖析源代码,根据文档中的描述来看以及经验来看,都应该想到这里的request就是

$_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER 中携带的信息,虽然会做一些处理,但是一个基本的http请求中都必须包含这些信息,这是Http协议决定了

接着是内核处理这个请求

$response = $kernel->handle($request);

这个地方就勉为其难的看一下源代码好了,目光定位到Illuminate\Foundation\Http\Kernel 的111行

顺便我们再精简一下,把异常处理去掉,得到代码如下

public function handle($request)
{
    $request->enableHttpMethodParameterOverride();
    $response = $this->sendRequestThroughRouter($request);
    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );
    return $response;
}

做了三件事情

  1. 开启http 方法重写 (这个地方其实仅仅是为了支持 传统表单页面中 用_method=put 来代替PUT请求这样的功能的)

    具体代码可以查看Symfony\Component\HttpFoundation\Request 1229行

  2. sendRequestThroughRouter发送请求到路由器并得到响应$response

  3. 分发事件处理

发送请求到路由器

这里是发送请求到路由器 那么接下来肯定是路由分发了

目光定位到Illuminate\Foundation\Http\Kernel 149行代码

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
        ->send($request)
        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
        ->then($this->dispatchToRouter());
}

只需要看最后一行代码就可以了,这里有两个关键词

  • Pipeline 管道
  • dispatchToRouter 路由分发

记得最开始make内核的时候,在App\Http\Kernel 文件里有许多的中间件数组配置,那么这里的Pipeline做的事情只有一件,让这些左右的中间件变成一个队列一样,这些请求依次穿过(through)这些中间件, 注意这里的用词是through 穿过,然后then 路由分发dispatchToRouter , 读起来很有感觉

dispatchToRouter路由分发就更简单了,在laravel中所有的路由都是预先配置好的,通过request请求中的path找到配置好的route,如果命中则交给route中定义的控制器或者闭包来进行处理,如果没有命中,那么则抛出异常

当然 抛出异常这个过程是在上一步处理请求中的 handle中执行的,因为我精简掉了异常处理代码 所以看不到

最后route的dispatch过程,可以把代码定位到Illuminate\Routing\Roouter 730行

经过了一些列的操作,甚至包括中间件

在laravel中 请求和响应是都要经过中间件的 代码分别在

请求: Illuminate\Foundation\Http\Kernel 149行代码

响应: Illuminate\Routing\Roouter 656行

public static function toResponse($request, $response)
{
    if ($response instanceof Responsable) {
        $response = $response->toResponse($request);

    }

    if ($response instanceof PsrResponseInterface) {
        $response = (new HttpFoundationFactory)->createResponse($response);

    } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
        $response = new JsonResponse($response, 201);

    } elseif (! $response instanceof SymfonyResponse &&
        ($response instanceof Arrayable ||
        $response instanceof Jsonable ||
        $response instanceof ArrayObject ||
        $response instanceof JsonSerializable ||
        is_array($response))) {
        $response = new JsonResponse($response);

    } elseif (! $response instanceof SymfonyResponse) {
        $response = new Response($response);

    }

    if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
        $response->setNotModified();
    }

    return $response->prepare($request);
}

这里最后一定会得到一个Illuminate\Http\Response的实例

发送响应

$response->send();

根据上面所有的流程,我们最终得出, 返回了一个Response实例,那么接下来我们一起看看laravel 是怎么把这个发送这个response的

注意看这里 这个响应是自己发送的 并不是内核发送的 内核仅仅是根据请求 并且通过了管道内的中间件 最后得到了一个Response 实例

目光定位到Symfony\Component\HttpFoundation 的 373行代码

精简一下变成这样

public function send()
{
    $this->sendHeaders();
    $this->sendContent();
}
  • $this->sendHeaders(); 这行代码输出头信息 Symfony\Component\HttpFoundation 330行代码
header($name.': '.$value, $replace, $this->statusCode);
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

这个地方仅仅只是利用header 输出了一些头部信息

  • $this->sendContent(); 这行代码输出文本
public function sendContent()
{
    echo $this->content;
    return $this;
}

和预想中的一样,这个地方仅仅是echo了一些东西而已 没有特殊的地方

通过代码分析 我们可以知道 $response->send();只做了两件事情 输出头信息以及内容, 和输出所有的网页一样,头部和内容

内核终止请求与响应

$kernel->terminate($request, $response);

目光定位到Illuminate\Foundation\Application 中的1006行代码

public function terminate()
{
    foreach ($this->terminatingCallbacks as $terminating) {
        $this->call($terminating);
    }
}

这里的代码很简单,在响应成功之后,执行一些回调函数

总结

抛开所有的类库 框架 mvc等等各种各样的概念来看,laravel其实是一个非常简单的东西,当然http协议也是一样

创建应用实例->接受请求->得到响应->发送响应(header 一些东西 echo 一些东西)->结束