一个后端的前端学习之旅——3.喜闻乐见的跨域问题

决定js框架

在写完 一个后端的前端学习之旅——1.决定学什么 后我决定用coffeescript来看一些js框架,本来想用react,结果发现用它跟gulp配起来略烦,选来选去选择了小半天决定最终用 spinejs, 看起来比较小,而且源码有coffee和js两个版本,还方便看,当然我知道这不是什么主流框架,可能文档什么的少一些,但这样更可以看出看源码的重要性,无论是不是python。

好的,开始学习

首先脚手架的项目就丢到一边,基于脚手架建了一个项目 leaning-frontend, 由于我把bower,npm的安装文件放到git中管理,项目比较大,github传输太慢了,先放着开源中国的git上。

然后bower装了下spine,npm装了下spine,我知道bower装的东西会被gulp编译到vendor.js里面,然而貌似我不用npm装spine的话在coffee里面会require报错(我暂时没有管require到底干毛的,看起来是import),bower装的spine有个问题,因为gulp中采用了一个main-bower-file还是什么东西的找包的主文件,然后gulp讲这个文件粘贴到vendor.js里面,但是看起来spine是模块分离的,需要单独引用(或者是其他什么原因),它并没有bower.json。所以我修改了下gulp中的tasks/bower.coffee将所有的外部库丢到一个文件夹里面这样可以直接引用(有没有更好的方案之后再说)。

第一步目录结构

spine是mvc的,然而都是js,所以我在source下面建了controllers、models、views这三个文件夹,views里面采用eco这么种东西,看起来很像djang的模板。

文档

class Contact extends Spine.Model
  @configure "Contact", "name"
  @extend Spine.Model.Ajax

  @url: "/users
  
  
class App extends Spine.Controller
  constructor: ->
    super
    # Instantiate other controllers..
    Photo.fetch()      
    

根据这个文档我知道了继承Model后@extend一个Spine.Model.Ajax然后添加一个url就可以调用Model.fetch()了,so我在main.coffee里面试了一下然后就出了些事情,容我慢慢道来。

我准备用学堂在线的某些api做一些事情: http://www.xuetangx.com/api/v2/courses,下面是我的Model。


class Course extends Spine.Model
  @configure "Course", "name"
  @extend Spine.Model.Ajax
  @url: "/api/v2/courses"

module.exports = Course

fecth调用的时候首先404了,因为gulp在dev时启动的是一个localhost的本地服务器(改成0.0.0.0:3000了), spine在请求的时候url是相对路径所以拼上的,好的吧,那我写绝对路径 @url: http://www.xuetangx.com/api/v2/courses, 然而他依然是拼接的(http://192.168.9.191:3000/http://www.xue...),我丢你老母。

然后继续看文档发现了 Spine.Model.host = "http://my-endpoint",强行把host设走,我就在main.coffee第一行把host设置了学堂在线的host,然后 就跨域了

喜闻乐见的跨域问题

首先我们知道,跨域是浏览器的某种安全限制,服务器端发起request并不存在跨域问题(这也是你可以愉快的用脚本requests.get或者写爬虫的原因)。
解决跨域问题有很多方案:

  1. 如果外域本来就是你的,并且你觉得允许所有人访问的话(一般不用,出发你就是一个专门的api服务器),nginx配上三行代码即可(一搜就有)

  2. 同1,只是不是nginx上加,而是在代码里面做一些事情,例如django,用django-cors-headers

  3. chrome可以关闭跨域 启动的时候加上一个参数即可C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe --disable-web-security

  4. 写一个proxy,这样你就有了1 2两点的条件随便搞

我选择的是4+2

proxy server

根据上面的方案选择我在项目根目录用django建了一个simple_server


cd 你的项目位置/leaning-frontend/simple_server
pip install -r requirements.txt # 如果你懂python请用virtualenv什么的
./manage.py runserver 0.0.0.0:12345 # 12345是你想运行的端口号

讲解

urls.py, 只有一个url所有的请求都用CrossDomainAjaxView来处理

url(r'^(?P.*)$', CrossDomainAjaxView.as_view(), name='ajax-hanler')

views.py, 这个view是做这么一件事情的,将请求的url通过requests这个三方库在我的服务器端请求外域服务器,然后讲response转变成django的response来返回给前端,暂时只写了get(非get django要穿csrf什么的比较烦)。这里有件事情想讲一下:

  1. 请求的header,我本来是想把接受到的所有header丢给requests的然而获取不到数据,暂时不管;

  2. response的header,我本来也是想把所有的response header都带回来的,然而并不行,python级别的东西报了一个http 1.1有关的异常is_hop_by_hop,所以我也暂时没带过来。

  3. rest framework仅仅是我想用他的view支持下非get post请求(然而我并没做具体处理,实际上没有任何作用)

# -*- coding: utf-8 -*-
#from rest_framework.views import View
#from rest_framework.generics import GenericAPIView as View
from django.views.generic import View
from django.conf import settings
from simple_server.utils import get_request_header, get_request_headers
from urlparse import urljoin
from django.http.response import HttpResponse
from wsgiref.util import is_hop_by_hop

import requests


class CrossDomainAjaxView(View):

    def transform_request_data(self, request, request_url):
        cross_domain_host = get_request_header(request, 'cross_domain_host') or settings.CROSS_DOMAIN_HOST
        url = urljoin(cross_domain_host, request_url)
        headers = get_request_headers(request)
        data = dict(request.GET)
        data.update(dict(request.POST))
        request_data = {
            'cross_domain_host': cross_domain_host,
            'url': url,
            'data': data,
            'headers': headers,
        }
        return request_data

    def transform_response(self, requests_response):
        response = HttpResponse(requests_response.content, content_type=
                requests_response.headers.get('Content-Type', 'text/plain'))
        return response


    def get(self, request, request_url):
        request_data = self.transform_request_data(request, request_url)
        response = requests.get(request_data['url'], request_data['data'])
                #headers=request_data['headers'])
        print response.content
        return self.transform_response(response)

settings.py

middleware加上这两个来允许跨域'corsheaders.middleware.CorsMiddleware'CORS_ORIGIN_ALLOW_ALL = True, 然后由于前端传的是相对路径所以我加了一个host CROSS_DOMAIN_HOST = 'http://www.xuetangx.com', get的时候支持在header里面改变不同的host支持不同网站的api调用而不是写死一个。

well done

chrome的Network的XHR里面这次看到请求了,哇哈哈拦路虎解决。

oh对了,顺便fix了一些gulp对于spine需要支持的东西,主要是eco。

btw,发现学堂在线一个交互做的不错的页面 (好吧其实是产品强行让我贴的友链,各位随意)

2016-03-30更新

nginx可以添加location来直接通过反向代理跨域


  location /xuetangx {
    rewrite ^.+xuetangx/?(.*)$ /$1 break;
    include uwsgi_params;
    proxy_pass http://www.xuetangx.com/;
  }

  location /videoid2source/ {
    #include uwsgi_params;
    proxy_pass http://www.xuetangx.com/videoid2source/;
  }

你可能感兴趣的:(服务器开发,跨域,spine,前端)