Day 09 - Amazon Linux 2 上解决跨来源资源共用 (CORS) 与开机自动启动 uwsgi

33 篇文章 6 订阅
12 篇文章 0 订阅

Day 09 - Amazon Linux 2 上解决跨来源资源共用 (CORS) 与开机自动启动 uwsgi

在应用的后端,我们已经解决了以下几个问题:

  • 资料库存取: MariaDB。
  • RESTful API 实作:使用 post 上传图片。
  • 后端伺服器整合:Nginx 并合并 Django。

最后还有两个问题待解决:跨来源资源共用与自动启动。跨来源资源共用 (Cross-Origin Resource Sharing, CORS) 是指当使用者代理请求一个不是目前文件来源,例如来自于不同网域(domain)、通讯协定(protocol)或通讯埠(port)的资源时,会建立一个跨来源 HTTP 请求(cross-origin HTTP request)。这种情况最常发生在手机应用 WebView 的方式来开发,通常很多号称跨手机平台开发的框架就是利用 WebView 来进行开发,开发者只要撰写 HTML + JavaScript + CSS 的网页语法,只要封装在 WebView 的元件里,就可以轻松的布署到不同的装置,如 iPhone, 安卓上。但是这样的网页设计就会涉及到跨来源资源共用 (CORS) 的问题,因为手机端的网页通常是 http://127.0.0.1/index.html 或是 file:///index.html ,而我们所设计的 RESTful API 却是 http://[EC2_IPv4]/imgUpload/ ,很明显的,网域就截然不同。

以下我们简单撰写一个例子来测试,并将这个档案分别放置到 EC2 以及自己的电脑上,来观察它的运行结果。下图是我们代码的部份内容,可以看出第一部分是设定上传的栏位名称为 fileUpload;第二部分是透过 POST 的方式,指定呼叫特定的 RESTful API;第三部分则是针对回应的结果进行分析并显示。

在这里插入图片描述
图 1、 上传图片的部分 JavaScript 代码

接着透过终端机或是 putty 连线到 EC2 ,在 upload 这个目录下建立这个上传图片的测试网页 fishrecog.html ,完整代码置于附录中,记得先启动 uwsgi 这个程序,方可存取 RESTful API 。

# 切换到正确的目录
cd /home/ec2-user/fishRecognition/fishsite
# 启用 uwsgi,方可存取 RESTful API
uwsgi --ini uwsgi.ini
# 这是静态网页的所在目录
cd upload/
# 创建测试网页文件
vi fishrecog.html

下图中,可以看到,当拖拉一张图到指定的虚线方块时,就会呼叫 impUpload 这个 RESTful API,并在图片下方显示出回应的结果。

在这里插入图片描述
图 2、 EC2 上的测试网页结果

但是当我们把一样的网页透过本地的方式 (file:///[LOCALPATH]/fishrecog.html) 打开时。同样的操作却没有任何回应回传回来,打开 Chrome 的开发者工具,在 Console 视窗下就可以看到错误原因就是 CORS 所造成,而 CORS 的安全限制是浏览器所造成,当发生 CORS 请求时,浏览器会询问伺服器是否支援,若不支援,就会发出错误讯息。

在这里插入图片描述
图 3、 本地主机上的测试网页结果

所以必须要在 Django 中打开 CORS 的存取限制,打开 settings.py 设定一个中间件,由中间件来回应 CORS 的请求,如下图所示。

fishsite/settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'fishsite.cors.CorsMiddleware',
]

在这里插入图片描述
图 4、 在 settings.py 中新增中间件设定

新增中间件文件

关于 CORS 的请求,可以分成下列两种:

  • 简单请求:一次请求,可以指定允许的来源 Access-Control-Allow-Origin ,可以指定,若不指定就用 * 表示同意所有来源的请求。
  • 非简单请求:两次请求,在发送数据之前会先发一次请求用于做「预检」(preflight),只有「预检」通过后才再发送一次请求。

预检内容如下:

  • 请求方式:OPTIONS
  • “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
  • 如何”预检”
    • 如果复杂请求是 PUT 等方法请求,则服务端需要设置允许某请求方法,否则”预检”不通过
      * Access-Control-Request-Method = GET, POST, ...
    • 如果复杂请求设置了请求内容表头,则服务端需要设置允许某请求内容表头,否则”预检”不通过
      Access-Control-Request-Headers = text/plain,text/html, ...

相关实作代码如下。

fishsite/cors.py

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings

class CorsMiddleware(MiddlewareMixin):

    def process_response(self,request,response):
        response['Access-Control-Allow-Origin'] = '*'
        #response['Access-Control-Allow-Credentials'] =  'true'
        if request.method == "OPTIONS":
            response["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE"
            response["Access-Control-Allow-Headers"] = "Content-Type"
            response.status_code = 200
        return response

在这里插入图片描述
图 5、 确认所有档案相关位置

因为已经修改过 Django,所要要重新启动 uwsgi,要先删除原先的 uwsgi 行程

ps aux | grep uw
kill -9 8300 8302
uwsgi --ini uwsgi.ini

在这里插入图片描述
图 6、重新启动 uwsgi

接着回到本地端的浏览器,找到刚刚已本地档案开启模式的画面,再重新拖拉一张图片过去,可以在画面下方看到 RESTful API 的回传结果;在开发者工具视窗,选择网路 (network) 功能页面,可以发现浏览器会送出两次 imgUpload 的请求,第一次是预检,可以发现请求方法是 OPTIONS,预检请求成功后,才会送出第二次实际的请求。

在这里插入图片描述
图 7、重新执行本地端网页操作

完成了 CORS后,最后希望可以自动启动所有服务,包含 uwsgi ,所以我们需要手动设定,让每次系统启动时,都会自动启动 uwsgi,在 Linux 中有一个 /etc/rc.local 档案,每次系统启动后就会自动执行这个草稿 (script) 档案,所以只需要将要执行的命令放入该档案内即可,我们在档案最后加上一行指令,详细相关位置如下图所示。

/etc/rc.local

# 以 ec2-user 使用者身分执行命令 
su ec2-user -c 'cd /home/ec2-user/fishRecognition/fishsite;/home/ec2-user/fishRecognition/bin/uwsgi -d --ini uwsgi.ini'

在这里插入图片描述
图 8、在开机执行档中加入 uwsgi 指令

只要重新启动 EC2 在运行先前的画面就可以确认是否生效。

参考资料

  • 跨來源資源共用(CORS), https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS
  • Cross-Origin Resource Sharing (CORS), https://web.dev/cross-origin-resource-sharing/
  • How to Enable CORS in NGINX, https://ubiq.co/tech-blog/enable-cors-nginx/
  • Status Codes, https://www.django-rest-framework.org/api-guide/status-codes/
  • python 全栈开发,Day100(restful 接口,DRF组件,DRF跨域(cors组件)), https://www.cnblogs.com/xiao987334176/articles/9457580.html
  • Execute script from rc.local as user instead of root, https://serverfault.com/questions/422950/execute-script-from-rc-local-as-user-instead-of-root

附录

fishrecog.html

<head>
<meta charset="utf-8"/>
<title>观赏鱼辨识系统</title>
<style>
#holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto;}
#holder.hover { border: 10px dashed #0c0; }
#holder img { display: block; margin: 10px auto; }
#holder p { margin: 10px; font-size: 14px; }
progress { width: 100%; }
progress:after { content: '%'; }
.fail { background: #c00; padding: 2px; color: #fff; }
.hidden { display: none !important;}
</style>
</head>
<body>
<article>
  <div id="holder">
  </div> 
  <p id="upload" class="hidden"><label>不支援拖拉方式,但可以直接选择图片:<br><input type="file"></label></p>
  <p id="filereader">File API &amp; FileReader API not supported</p>
  <p id="formdata">XHR2's FormData is not supported</p>
  <p id="progress">XHR2's upload progress isn't supported</p>
  <p>Upload progress: <progress id="uploadprogress" max="100" value="0">0</progress></p>
  <p>观赏鱼辨识系统可以直接拖拉一个图片到虚线区</p>
  <div id="result"></div>
</article>
<script>
var holder = document.getElementById('holder'),
    tests = {
      filereader: typeof FileReader != 'undefined',
      dnd: 'draggable' in document.createElement('span'),
      formdata: !!window.FormData,
      progress: "upload" in new XMLHttpRequest
    }, 
    support = {
      filereader: document.getElementById('filereader'),
      formdata: document.getElementById('formdata'),
      progress: document.getElementById('progress')
    },
    acceptedTypes = {
      'image/png': true,
      'image/jpeg': true,
      'image/gif': true
    },
    progress = document.getElementById('uploadprogress'),
    fileupload = document.getElementById('upload');

"filereader formdata progress".split(' ').forEach(function (api) {
  if (tests[api] === false) {
    support[api].className = 'fail';
  } else {
    // FFS. I could have done el.hidden = true, but IE doesn't support
    // hidden, so I tried to create a polyfill that would extend the
    // Element.prototype, but then IE10 doesn't even give me access
    // to the Element object. Brilliant.
    support[api].className = 'hidden';
  }
});

function previewfile(file) {
  if (tests.filereader === true && acceptedTypes[file.type] === true) {
    var reader = new FileReader();
    reader.onload = function (event) {
      var image = new Image();
      image.src = event.target.result;
      image.width = 250; // a fake resize
      holder.appendChild(image);
    };

    reader.readAsDataURL(file);
  }  else {
    holder.innerHTML += '<p>文档已上传 ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : '');
    console.log(file);
  }
}

function readfiles(files) {
    //debugger;
    var formData = tests.formdata ? new FormData() : null;
    for (var i = 0; i < files.length; i++) {
      if (tests.formdata) formData.append('fileUpload', files[i]);
      previewfile(files[i]);
    }

    if (tests.formdata) {
      var xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://13.229.139.117/imgUpload/');
      xhr.onreadystatechange = function() {
			if (xhr.readyState == XMLHttpRequest.DONE) {
				result = document.getElementById('result');
				var tr=xhr.responseText;
				var utr=unescape(tr.replace(/\\u/gi, '%u'));
				//jsonarr = eval('(' + xhr.responseText + ')');
				var arr = JSON.parse(utr);

				console.log(arr);
				var str ='<p>鱼名:'+arr[0].fishName+'</p><p>识别数量:'+arr[0].fishQtn+'</p><p>拉丁名:'+arr[0].LatinName+'</p><p>出没地区:'+arr[0].distribution+'</p>'; 
				result.innerHTML = str;
			}
		}
      xhr.onload = function() {
        progress.value = progress.innerHTML = 100;
      };

      if (tests.progress) {
        xhr.upload.onprogress = function (event) {
          if (event.lengthComputable) {
            var complete = (event.loaded / event.total * 100 | 0);
            progress.value = progress.innerHTML = complete;
          }
        }
      }

      xhr.send(formData);
    }
}

if (tests.dnd) { 
  holder.ondragover = function () { this.className = 'hover'; return false; };
  holder.ondragend = function () { this.className = ''; return false; };
  holder.ondrop = function (e) {
    this.className = '';
    e.preventDefault();
    readfiles(e.dataTransfer.files);
  }
} else {
  fileupload.className = 'hidden';
  fileupload.querySelector('input').onchange = function () {
    readfiles(this.files);
  };
}

</script>

</body>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值