专栏首页携程技术干货 | 前端常用的通信技术

干货 | 前端常用的通信技术

作者简介

陈为平,携程市场部前端工程师,目前主要负责“携程运动”项目的大前端相关工作。

前段时间在忙开发携程运动项目和相应的微信小程序,其中和后端通信犹为频繁。get、post请求方法是很多前端童鞋使用最频繁的;websocket在11年盛行后方便了客户端和服务器之间传输,……and so on ,除了这些,还有很多我们不常使用的其他方式,但是在实际的业务场景中却真实需要。

本文总结了目前前端使用到的数据交换方式,阐述了业务场景中如何选择适合的方式进行数据交换( form ,xhr, fetch, SSE, webstock, postmessage, web workers等),并列举了一些示例代码, 可能存在不足的地方,欢迎大家指正。

本文用到的源代码都放在Github上,点击下方阅读原文可直达。

关于HTTP协义基础可以参考阮一峰老师的《HTTP协议入门》一文。

前端经常使用的HTTP协议相关(1.0 / 1.1)

method

· GET ( 对应 restful api 查询资源, 用于客户端从服务端取数据 )

· POST(对应 restful api中的增加资源, 用于客户端传数据到服务端)

· PUT (对应 restful api中的更新资源)

· DELETE ( 对应 restful api中的删除资源 )

· HEAD ( 可以用于http请求的时间什么,或者判断是否存在判断文件大小等)

· OPTIONS (在前端中常用于 cors跨域验证)

· TRACE * (我这边没有用到过,欢迎补充)

· CONNECT * (我这边没有用到过,欢迎补充)

enctype

· application/x-www-form-urlencoded (默认,正常的提交方式)

· multipart/form-data(有上传文件时常用这种)

· application/json (ajax常用这种格式)

· text/xml

· text/plain

enctype示例说明( form , ajax, fetch 三种示例 )

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>enctype</title>
    <style>
        .box{border: 1px solid #ccc;padding:20px;}
        .out{background: #efefef; padding:10px 20px; margin-top: 20px;}
    </style>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.3.min.js"></script>
    
    <script>

    $(function(){

        $('#b1').on('click', function(){
            $.ajax({
                method: "POST",
                contentType:'application/x-www-form-urlencoded;charset=UTF-8',
                url: "form_action.php",
                data: {username: "John", password: "Boston" }
            }).done(function( msg ) {
                $('#msg1').html(msg);
            });
        });

        $('#f1').on('click', function(){
            fetch("form_action.php", {
                method: "POST",
                credentials: 'include', //带上cookie
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
                },
                body: "username=John&password=Boston"
            })
            .then(function(response){
                return response.text();
            })
            .then(function(msg) {
                $('#msg1').html(msg);

            }, function(e) {
                alert("Error submitting form!");
            });
        });


        $('#b2').on('click', function(){
            var formData = new FormData(document.querySelector("#data2"));
            $.ajax({
                method: "POST",
                processData:false, //无需让jquery正处理一下数据
                contentType:false, //已经是formData就默认为 multipart/form-data
                cache: false,
                url: "form_action.php",
                data: formData
            }).done(function( msg ) {
                $('#msg2').html(msg);
            });
        });

        $('#f2').on('click', function(){
            var formData = new FormData(document.querySelector("#data2"));
            fetch("form_action.php", {
                method: "POST",
                headers: {
                    "Content-Type": "multipart/form-data;charset=UTF-8"
                },
                body: formData
            })
            .then(function(response){
                return response.text();
            })
            .then(function(msg) {
                $('#msg2').html(msg);
            }, function(e) {
                alert("Error submitting form!");
            });
        });


        $('#b3').on('click', function(){
            $.ajax({
                method: "POST",
                contentType:'application/json;charset=UTF-8',
                url: "form_action.php",
                data: JSON.stringify({username: "John", password: "Boston" })
            }).done(function( msg ) {
                $('#msg3').html(msg);
            });
        });

        $('#f3').on('click', function(){
            var formData = new FormData(document.querySelector("#data2"));
            fetch("form_action.php", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json;charset=UTF-8"
                },
                body: JSON.stringify({username: "John", password: "Boston" })
            })
            .then(function(response){
                return response.text();
            })
            .then(function(msg) {
                $('#msg3').html(msg);
            }, function(e) {
                alert("Error submitting form!");
            });
        });

        $('#b4').on('click', function(){
            $.ajax({
                method: "POST",
                contentType:'text/plain;charset=UTF-8',
                processData:false, //无需让jquery正处理一下数据
                url: "form_action.php",
                data: "我是一个纯正的文本功能!\r\n我第二行"
            }).done(function( msg ) {
                $('#msg4').html(msg);
            });
        });

        $('#f4').on('click', function(){
            var formData = new FormData(document.querySelector("#data2"));
            fetch("form_action.php", {
                method: "POST",
                headers: {
                    "Content-Type": "text/plain;charset=UTF-8"
                },
                body: "我是一个纯正的文本功能!\r\n我第二行"
            })
            .then(function(response){
                return response.text();
            })
            .then(function(msg) {
                $('#msg4').html(msg);
            }, function(e) {
                alert("Error submitting form!");
            });
        });

        $('#b5').on('click', function(){
            $.ajax({
                method: "POST",
                contentType:'text/xml;charset=UTF-8',
                // processData:false, //无需让jquery正处理一下数据
                url: "form_action.php",
                data: "<doc><h1>我是标签</h1><p>我是内容</p></doc>"
            }).done(function( msg ) {
                $('#msg5').html(msg);
            });
        });

        $('#f5').on('click', function(){
            var formData = new FormData(document.querySelector("#data2"));
            fetch("form_action.php", {
                method: "POST",
                headers: {
                    "Content-Type": "text/xml;charset=UTF-8"
                },
                body: "<doc><max>我是XML标签</max><min>我是XML内容</min></doc>"
            })
            .then(function(response){
                return response.text();
            })
            .then(function(msg) {
                $('#msg5').html(msg);
            }, function(e) {
                alert("Error submitting form!");
            });
        });


    });
    </script>
</head>
<body>

<h1>enctype测试</h1>

<h2>表单提交: application/x-www-form-urlencoded</h2>
<div class="box">
    <form action="form_action.php" enctype="application/x-www-form-urlencoded" method="post">
        <p>用户: <input type="text" name="username" /></p>
        <p>密码: <input type="text" name="password" /></p>
        <input type="submit" value="提交" />
        <button type="button" id="b1">AJAX提交</button>
        <button type="button" id="f1">fetch提交</button>
    </form>
    <div id="msg1" class="out"></div>
</div>

<h2>multipart/form-data</h2>
<div class="box">
    <form id="data2" action="form_action.php" enctype="multipart/form-data" method="post">
        <p>用户: <input type="text" name="username" /></p>
        <p>密码: <input type="text" name="password" /></p>
        <p>文件: <input type="file" name="file" id="file1" /></p>
        <input type="submit" value="提交" />
        <button type="button" id="b2">AJAX提交</button>
        <button type="button" id="f2">fetch提交</button>
    </form>
    <div id="msg2" class="out"></div>
</div>

<h2>application/json</h2>
<div class="box">
    <form action="form_action.php" enctype="application/json" method="post">
        <p>用户: <input type="text" name="username" /></p>
        <p>密码: <input type="text" name="password" /></p>
        <input type="submit" value="提交" />
        <button type="button" id="b3">AJAX提交</button>
        <button type="button" id="f3">fetch提交</button>
    </form>
    <div id="msg3" class="out"></div>
</div>

<h2>text/plain</h2>
<div class="box">
    <form action="form_action.php" enctype="text/plain" method="post">
        <p>用户: <input type="text" name="username" /></p>
        <p>密码: <input type="text" name="password" /></p>
        <input type="submit" value="提交" />
        <button type="button" id="b4">AJAX提交</button>
        <button type="button" id="f4">fetch提交</button>
    </form>
    <div id="msg4" class="out"></div>
</div>

<h2>text/xml</h2>
<div class="box">
    <form action="form_action.php" enctype="text/xml" method="post">
        <p>用户: <input type="text" name="username" /></p>
        <p>密码: <input type="text" name="password" /></p>
        <input type="submit" value="提交" />
        <button type="button" id="b5">AJAX提交</button>
        <button type="button" id="f5">fetch提交</button>
    </form>
    <div id="msg5" class="out"></div>
</div>

</body>
</html>

服务端 form_action.php

<?php
echo '<pre>';

if($_POST){
    echo "<h1>POST</h1>";
    print_r($_POST);
    echo "<hr>";
}

if(file_get_contents("php://input")){
    echo "<h1>php://input</h1>";
    print_r(file_get_contents("php://input"));
    echo "<hr>";
}

if($_FILES){
    echo "<h1>file</h1>";
    print_r($_FILES);
    echo "<hr>";
}

* fetch api是基于Promise设计 * fetch 的一些例子 mdn/fetch-examples

服务器到客户端的推送 - Server-sent Events

这个是html5的一个新特性,主要用于服务器推送消息到客户端, 可以用于监控,通知,更新库存之类的应用场景, 在携程运动项目中我们主要应用于线上被预订后通知下发通知到场馆的操作界面上的即时改变状态。

图片来源于网络,侵删

优点: 基于http协义无需特别的改造,调试方便, 可以CORS跨域 server-send events 是服务端往客户端单向推送的,如果客户端需要上传消息可以使用 WebSocket

客户端代码

var source = new EventSource('http://localhost:7000/server');source.onmessage = function(e) {
    console.log('e', JSON.parse( e.data));
    document.getElementById('box').innerHTML += "SSE notification: " + e.data + '<br />';
};

服务端代码

<?php 
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
//数据
$time = date('Y-m-d H:i:s');
$data = array(
    'id'=>1,
    'name'=>'中文',
    'time'=>$time
);
echo "data: ".json_encode($data)."\n\n";
flush();
?>
echo "event: ping\n"; // 增加 event可以多送多个事件
js使用 source.addEventListener('ping', function(){}, false); 来处理对应的事件

对于低版本的浏览器可以使用 eventsource polyfill

  • Yaffle/EventSource by yaffle
  • https://github.com/remy/polyfills/blob/master/EventSource.js by Remy Sharp
  • rwaldron/jquery.eventsource by Rick Waldron
  • amvtek/EventSource by AmvTek

客户端与服务器双向通信 WebSocket

特点

1. websocket 是个双向的通信。 2. 常用于应用于一些都需要双方交互的,实时性比较强的地方(如聊天,在线客服) 3. 数据传输量小 4. websocket 是个 持久化的连接

原理图

图片来源于网络. 侵删

这个的服务端是基于 nodejs实现的(不要问为什么不是php,因为 nodejs 简单些!)

server.js

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 2000});

wss.on('connection', function(ws) {
    ws.send('服务端发来一条消息');
    ws.on('message', function(message) {
        //转发一下客户端发过来的消息
        console.log('收到客户端来的消息: %s', message);
        ws.send('服务端收到来自客户端的消息:' + message);
    });
    ws.on('close', function(event) {
        console.log('客户端请求关闭',event);
    });
});

client.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 双向通信</title>
    <style>
      #boxwarp > div{
        border: 1px solid #ccc;
        padding:10px;
        margin:10px;
      }
    </style>

</head>
<body>
<button id="btn">发点什么</button>
<div id="boxwarp"></div>
<script>
var ws = new WebSocket("ws://127.0.0.1:2000/");
document.getElementById('btn').addEventListener('click', function() {
  ws.send('cancel_order');
});
function addbox(msg){
  var box = document.createElement('div');
      box.innerHTML = msg;
  document.getElementById('boxwarp').append(box);
}
ws.onopen = function() {
    var msg = 'ws已经联接';
    addbox(msg);
    ws.send(msg);
};
ws.onmessage = function (evt) {
    console.log('evt');
    addbox(evt.data);
};

ws.onclose = function() {
   console.log('close');
   addbox('服务端关闭了ws');
};
ws.onerror = function(err) {
   addbox(err);
};
</script>
</body>
</html>

说完了客户端与服客端之间的通信,现在我们来聊聊客户端之间的通信。

客户端与客户端页面之间的通信 postMessage

主要特点

1. window.postMessage() 方法可以安全地实现跨域通信 2.主要用于两个页面之间的消息传送 3. 可以使用iframe与window.open打开的页面进行通信.

特别的应用场景 我们的页面引用了其他的人页面,但我们不知道他们的页面高度,这时可以通过window.postMessages 从iframe 里面的页面来传到 当前页面. 语法

otherWindow.postMessage(message, targetOrigin, [transfer]);

示例代码 postmessage.html (入口)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>postmessage示例</title>
    <style>
        html,body{height: 100%;}
        *{padding: 0; margin:0;}
        .warp{ display: flex; }
        .warp > div,
        .warp > iframe{
            flex: 1;
            margin:10px;
        }
        iframe{
            height: 600px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
<div class="warp">
    <div class="left">
        左边页面    </div>
    <div class="right">
        右边页面    </div></div><div class="warp">
    <div class="left warp">
        <iframe src="./post1.html" frameborder="0" id="post1" name="post1"></iframe>
    </div>
    <div class="right warp">
        <iframe src="./post2.html" frameborder="0" id="post2" name="post2"></iframe>
    </div>

<!-- window.frames[0].postMessage('getcolor','http://lslib.com'); -->
</div>

<div class="warp">
    <div class="left"><button id="postBtn1">向左边的(iframe)推送信息代码</button></div>
    <div class="right"><button id="postBtn2">向右边的(iframe)推送信息代码</button></div>
</div>

<script>
document.getElementById('postBtn1').addEventListener('click', function(){
    console.log('postBtn1');
    var date = new Date().toString();
    window.post1.postMessage(date,'*');});document.getElementById('postBtn2').addEventListener('click', function(){
    console.log('postBtn2');
    var date = new Date().toString();
    window.post2.postMessage(date,'*');});window.addEventListener('message',function(e){
    if(e.data){
        console.log(e.data);
        console.log(e);
        window.post1.postMessage(e.data,'*');
    }},false);
</script>
</body>
</html>

post1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .sendbox{
            background: #efefef;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
<div class="sendbox">
    <button id="sendbox2">直接发送到右边iframe</button>
    左边的iframe
</div>
<div id="box2">
</div>
<script>
    document.getElementById('sendbox2').addEventListener('click', function(){
        window.parent.post2.postMessage('收到来自左边ifarme的消息' + +new Date(),'*');
    });
    function addbox(html){
        var item = document.createElement('div');
        item.innerHTML = html;
        document.getElementById('box2').append(item);
    }
    window.addEventListener('message',function(e){
        if(e.data){
            addbox(e.data);
        }
    },false);
</script>

</body>
</html>

post2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .sendbox{
            background: #ccc;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
<div class="sendbox" style="text-align: right;">
    <button id="sendbox">中转到左边</button>
    <button id="sendbox2">直接到左边</button>
    右边的iframe
</div>
<div id="box"></div>

<script>
    document.getElementById('sendbox').addEventListener('click', function(){
        /*- 向父级页面传 -*/
        window.parent.postMessage('来自post2的消息' + +new Date(),'*');
    });
    document.getElementById('sendbox2').addEventListener('click', function(){
        window.parent.post1.postMessage('直接来自右边' + +new Date(),'*');
    });
    function addbox(html){
        var item = document.createElement('div');
        item.innerHTML = html;
        document.getElementById('box').append(item);
    }
    window.addEventListener('message',function(e){
        if(e.data){
            addbox(e.data);
        }
    },false);
</script>

</body>
</html>

Web Workers 进程通信(html5中的js的后台进程)

javascript设计上是一个单线,也就是说在执行js过程中只能执行一个任务, 其他的任务都在队列中等待运行。

如果我们执行大量计算的任务时,就会阻止浏览器执行js,导致浏览器假死。 html5的 web Workers 子进程 就是为了解决这种问题而设计的。把大量计算的任务当作类似ajax异步方式进入子进程计算,计算完了再通过 postmessage通知主进程计算结果。

图片来源于网络. 侵删

主线程代码(index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .box-warp > div{
            border: 1px solid #ccc;
            margin:10px;
            padding:10px;
        }
    </style>
</head>
<body>
    <button id="btn">开启一个后台线程(点击外框中止线程)</button>
    <div class="box-warp" id="boxwarp"></div>

    <script>
    var id = 1;
    function init_works(){
        var warpid = 'box'+id;
        var box = document.createElement('div');
            box.id = warpid;
        document.getElementById('boxwarp').append(box);
        var worker = new Worker('./compute.js');
        //监听后台进程发过来的消息
        worker.onmessage= function (event) {
            // 把子线程返回的结果添加到 div 上
            document.getElementById(warpid).innerHTML += event.data+"<br/>";
        };
        //点击中止后端进程
        box.addEventListener('click', function(){
            worker.postMessage("oh, 我被干掉了" + warpid);
            var time = setTimeout(function(){
                worker.terminate();
                clearTimeout(time);
            },0);
        });
        //往后台线程发送消息
        worker.postMessage("hi, 我是" + warpid);
        id++;
    }

    document.getElementById('btn').addEventListener('click', function(){
        init_works();
    });
    </script>
</body>
</html>

后台进程代码( compute.js )

var i=0;
function timeX(){
    i++;
    postMessage(i);
    if(i>9){
        postMessage('no 我不想动了');
        close(); //中止线程
    }
    setTimeout(function(){
        timeX();
    },1000);
}

timeX();

//收到主线程的消息

onmessage = function (oEvent) {
  postMessage(oEvent.data);
};

上述代码简单的说明一下, 主进程与后台进程之间的互相通信。

本文分享自微信公众号 - 携程技术中心(ctriptech),作者:陈为平

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-06-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 敏捷转型,以用户为中心——携程的项目管理实践

    移动互联网时代,产品更新迭代日新月异,企业如何永葆活力不断创新呢?在“2016中国IT项目管理大会”上,携程作为中国旅游电商首席提出,积极拥抱变化,合力前行,但...

    携程技术
  • 技术人生 | 技术人如何打造个人品牌

    编者:本文来自携程框架研发部高级经理魏晓军在内部活动中分享,介绍了其在撰写国内第一本React Native相关书籍《ReactNative入门与实战》时的经历...

    携程技术
  • 干货 | 携程酒店RSocket实践

    刘诚,携程酒店研发性能架构师。2014年加入携程,致力于通过架构的演进,控制企业硬件成本。

    携程技术
  • Day3:html和css

    样式的显示效果是跟html元素中的类名先后顺序无关,而是跟css样式的书写上下顺序有关.

    达达前端
  • Vue.js简介

    Vue.js 是一个基于 MVVM 模型的 web 库。通过双向数据绑定连接View和Model层。实际的 DOM 操作被封装成 Directives 和 Fi...

    IMWeb前端团队
  • Vue.js简介

    Vue.js 是一个基于 MVVM 模型的 web 库。通过双向数据绑定连接View和Model层。实际的 DOM 操作被封装成 Directives 和 Fi...

    IMWeb前端团队
  • 04 . Vue组件注册,组件间数据交互,调试工具及组件插槽介绍及使用

    常见_youmen
  • 为何面向手写代码常被吐槽,但其仍未动摇?

    又一年金三银四到来,似乎 “面向手写/机写代码面试” 已经成为一些公司的必备面试之一了,在互联网面试中也占据着重要地位,当被面试手写代码时,刚开始自信心爆棚,有...

    五月君
  • 自从学会Python后,这样的牌局可以轻易破解,想输都难

    目前Python已经被应用于各个领域,即使是这样的牌局,也可以用Python算出最佳的出牌方案。

    Python编程大咖
  • 疯狂ios讲义之使用CoreLocati

    9.3  方向监测 拥有GPS硬件的设备可以生成设备的当前方向(course属性)和速度信息。iPhone设备携带的定位管理器可以返回一个已经计算好的cour...

    py3study

扫码关注云+社区

领取腾讯云代金券