报错原因
- 下面的xhr报错:login.html:62 Mixed Content: The page at 'https://drinkflower.asia/login.html' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://cloud.drinkflower.asia/'. This request has been blocked; the content must be served over HTTPS.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试</title>
<link rel="stylesheet" href="./layui/css/layui.css">
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.2.1/jquery.min.js"></script>
<!-- Matomo -->
<!-- End Matomo Code -->
<script>var wrong=0</script>
</head>
<body>
<div class="loginBox">
<h2>测试</h2>
<form action="" onsubmit="handleFormSubmit(event)">
<div class="item">
<input id="miao" type="text" required>
<label for="">用户名</label>
</div>
<div class="item">
<input id="wang" type="password" required>
<label for="">密码</label>
</div>
<div class="item" style="text-align:left;width:250px;display: inline-block;">
<input id="zheng" type="text" style="width: 100%;" required>
<label for="">验证码</label>
</div>
<div id='img1' style="display: inline-block;">
<img style="width: 60px; height: 30px;margin-left: auto;" onclick=javascript:refresh_code() id="imgcode" src="https://drinkflower.asia/api/verifycode.php" alt="验证码" />
</div>
<button id='bb' class="btn" onclick="gua()">登录
<span></span>
<span></span>
<span></span>
<span></span>
</button>
<a id="res" href="./res.html" style="position: absolute; bottom: 190px; right: 670px;font-size: smaller; color: rgb(255, 255, 255);">还没有账号?立即注册</a>
<script>
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://cloud.drinkflower.asia', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error('There was a problem with your XMLHttpRequest:', xhr.status);
}
}
};
xhr.send();
</script>
</body>
</html>
- 这是因为https的页面加载了http的资源,不管是静态资源还是api的调用,这会导致api的调用失败
- 最简单的解决办法是直接把提供api的服务加上证书弄成https的,或者弄一个断口转发得到服务,go的这类程序不少,而且很多都能加ssl,但是我的环境不是很方便
问题解决
- 这里我选择使用php做转发,贴上网上嫖的一个转发的类和调用实例
<?php
class php_proxy{
// 发送get、post请求
public function request_option($request_url='', $method='post', $request_data=[], $to_json=false): string{
if (empty($request_url)) {
$back = '{"state":0, "msg":"request_url is null", "content":""}';
}else{
if ($method == 'post' || $method == 'POST'){
$body = http_build_query($request_data);
$options = [
'http' => [
'method' => 'POST', // 注意要大写
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $body,
'ignore_errors'=> true, // 忽略报错,直接返回接口内容
],
];
$context = stream_context_create($options);
$data = file_get_contents($request_url, false, $context);
}else if ($method == 'get'|| $method == 'GET'){
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $request_url);
curl_setopt($curl, CURLOPT_HEADER, 0); // 不抓取头部信息。只返回数据
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_TIMEOUT, (int)60000); // 超时设置
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 1表示不返回bool值
$data = curl_exec($curl);
// $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); // 获取接口状态码
curl_close($curl);
}else{
$data = '{"state":0, "msg":"method error. method is only in [get, post], options etc be not supported.", "content":""}';
}
$back = $data;
}
if ($to_json == true){
$res = json_encode($back, true);
}else{
$res = $back;
}
return $res;
}
// 获取完整网址
public function get_url(): string{
if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$url = 'https://';
}else{
$url = 'http://';
}
return $url.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI'];
}
// 是post
public function is_post(): bool{
return isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) == 'POST';
}
// 是get
public function is_get(): bool{
return isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) == 'GET';
}
// 生成内网网址:把外网网址解析到内网网址
public function make_request_url($host, $intranet): string{
//echo str_replace($host, $intranet, $this->get_url());
return str_replace($host, $intranet, $this->get_url()); // 实际代理地址(就是替换主网址或路径)
}
// 转发接口
// 请从此处调用
public function request_intranet($host, $intranet): string{
// 测试的内网请求地址:$request_url = http://127.0.0.l:8000/api.gen1/admin
$request_url = $this->make_request_url($host, $intranet);
if ($this->is_post()){
$request_array = $_REQUEST; // 请求参数数组
$back = $this->request_option($request_url, 'post', $request_array, false);
}else if ($this->is_get()){
$request_array = [];
$back = $this->request_option($request_url, 'get', $request_array, false);
} else{
$back = '{"state":0, "msg":"method error. method is only in [get, post], options etc be not supported.", "content":""}';
}
return $back;
}
}
?>
- 脚本可以区分get和post请求(也只能转发这两种),默认是从目标获取text/plain的返回值
- 比如php的生成
echo 1;
- python的生成
response = make_response("f")
response.status_code = 200
return response
- 也可以改request_option方法的to_json参数为true,就能进下面的if,接收json并且解码返回,或者自己对$back变量写一个转码就行
if ($to_json == true){
$res = json_encode($back, true);
}else{
$res = $back;
}
return $res;
- 调用这个类只需要传入下面几个参数
$host = "https://drinkflower.asia:443/test.php"; // 外网网址(主网址或有部分路径)
$intranet = 'https://drinkflower.asia:443/post.php'; // 内网网址(主网址或有部分路径)
$php_proxy = new php_proxy();
$back = $php_proxy->request_intranet($host, $intranet);
- 我们在可以看到,脚本对请求的处理是接收参数,然后重新对目标网址用curl发送请求,把request_intranet echo出来试试
- 所以$host参数必须是脚本所在文件的全部路径,否则其它的参数可能会被当作参数传给$intranet网址
完整脚本和一些问题
- 贴上完整的类和利用
<?php
class php_proxy{
// 发送get、post请求
public function request_option($request_url='', $method='post', $request_data=[], $to_json=false): string{
//echo $request_url;
if (empty($request_url)) {
$back = '{"state":0, "msg":"request_url is null", "content":""}';
}else{
if ($method == 'post' || $method == 'POST'){
$body = http_build_query($request_data);
$options = [
'http' => [
'method' => 'POST', // 注意要大写
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $body,
'ignore_errors'=> true, // 忽略报错,直接返回接口内容
],
];
$context = stream_context_create($options);
$data = file_get_contents($request_url, false, $context);
}else if ($method == 'get'|| $method == 'GET'){
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $request_url);
curl_setopt($curl, CURLOPT_HEADER, 0); // 不抓取头部信息。只返回数据
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_TIMEOUT, (int)60000); // 超时设置
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 1表示不返回bool值
$data = curl_exec($curl);
// $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); // 获取接口状态码
curl_close($curl);
}else{
$data = '{"state":0, "msg":"method error. method is only in [get, post], options etc be not supported.", "content":""}';
}
$back = $data;
}
if ($to_json == true){
$res = json_encode($back, true);
}else{
$res = $back;
}
return $res;
}
// 获取完整网址
public function get_url(): string{
if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$url = 'https://';
}else{
$url = 'http://';
}
return $url.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI'];
}
// 是post
public function is_post(): bool{
return isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) == 'POST';
}
// 是get
public function is_get(): bool{
return isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) == 'GET';
}
// 生成内网网址:把外网网址解析到内网网址
public function make_request_url($host, $intranet): string{
//echo str_replace($host, $intranet, $this->get_url());
return str_replace($host, $intranet, $this->get_url()); // 实际代理地址(就是替换主网址或路径)
}
// 转发接口
// 请从此处调用
public function request_intranet($host, $intranet): string{
// 测试的内网请求地址:$request_url = http://127.0.0.l:8000/api.gen1/admin
$request_url = $this->make_request_url($host, $intranet);
if ($this->is_post()){
$request_array = $_REQUEST; // 请求参数数组
$back = $this->request_option($request_url, 'post', $request_array, false);
}else if ($this->is_get()){
$request_array = [];
$back = $this->request_option($request_url, 'get', $request_array, false);
} else{
$back = '{"state":0, "msg":"method error. method is only in [get, post], options etc be not supported.", "content":""}';
}
return $back;
}
}
// 使用参数化查询构建 SQL 查询语句
$host = "https://drinkflower.asia:443/test.php"; // 外网网址(主网址或有部分路径)
$intranet = 'https://drinkflower.asia:443/post.php'; // 内网网址(主网址或有部分路径)
$php_proxy = new php_proxy();
$back = $php_proxy->request_intranet($host, $intranet);
echo $back;
?>
- 先发一个get用post.php测试一下效果,内容如下
<?php
header('Content-Type: text/plain');
if(isset($_GET)){
var_dump($_GET);
exit;
}
- 可以看到转发成功了,那么换成post呢
<?php
header('Content-Type: text/plain');
if(isset($_POST)){
echo var_dump($_POST);
exit;
}
- 也成功了,但是如果深入测试一下,在这里再传一个get,就会发现实际的post把get的参数也post进去了
-
这是因为原作者在request_intranet函数里面处理post的参数的时候,把内容存在了$_REQUEST超全局变量里面,把get的一起处理了,想要避免这个问题只需要把这个函数里面的
$request_array = $_REQUEST;
改成$request_array = $_POST;
就可以了,不过作者这么写可能有他的深意吧,能跑就不动他了😋 -
这个类还有一个问题,如果我们的脚本里面对post和get都有使用,那么get的请求就失效了(这曾经困扰了我很久😭),比如下面这个post.php,我们发送一个get
<?php
header('Content-Type: text/plain');
if(isset($_POST)){
echo var_dump($_POST);
exit;
}
if(isset($_GET)){
echo var_dump($_GET);
exit;
}
- 可以看到get的参数接收不到了,因为函数进了post的if,但是没有post任何参数,所以返回为空就exit了,解决方法是在所有的isset旁边再加上传参是否为空的判定
if(isset($_POST)&&$_POST!=null){
echo var_dump($_POST);
exit;
}
if(isset($_GET)&&$_GET!=null){
echo var_dump($_GET);
exit;
}
- 最后,php用curl一定顺手加上
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
,不知道为什么在我的环境里面证书出现了问题但是没有任何报错,导致请求成功,所有状态码都正常,但是请求不到数据