• 网络请求与远程资源


    JavaScript网络请求与远程资源

    • XMLHttpRequest对象
    • XMLHttpRequest事件
    • 源域Ajax限制
    • Fetch API
    • Streams API

    XMLHttpRequest对象

    XHR

    let xhr = XMLHttpRequest();
    // 最后一个参数表示请求是否异步
    xhr.open('get', 'example.php', false);
    // send方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传null。
    xhr.send(null);
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      alert(xhr.)
    } else {
      alert("Request was unsuccessful: " + xhr.status);
    }
    

    收到响应后,XHR对象的以下属性会被填充上数据。

    • responseText:作为响应体返回的文本。
    • responseXML:如果响应的内容类型是text/html或application/xml,那就是包含响应数据的xml,Dom文档。
    • status:响应的HTTP状态。
    • statusText:响应的HTTP状态描述。
    let xhr = new XMLHttpRequest();
    xhr.open('get', 'example.php', true);
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      alert(xhr.responseText);
    } else {
      alert("Request was unsuccessful: " + xhr.status);
    }
    

    XHR对象有一个readyState属性,表示当前在请求/响应过程的哪个阶段。

    • 0:未初始化(uninitialized)。尚未调用open()方法。
    • 1:已打开(Open)。已调用open()方法,尚未调用send()方法。
    • 2:已发送(Sent)。已调用send()方法,尚未收到响应。
    • 3:接收中(Receivering)。已收到部分响应。
    • 4:完成(Complete)。已经收到所有响应,可以使用了。

    每次readyState从一个值变成另一个值,都会触发readystatechange事件。

    在收到响应之前如果想要取消异步请求,可以调用abort()方法。

    HTTP头部

    默认情况下,XHR请求会发送以下头部字段。

    • Accept:
    • Accept-Charset
    • Accept-Encoding
    • Accept-Language
    • Connection
    • Cookie
    • Host:发送请求的页面所在的域
    • Referer:发送请求的页面的URL。
    • User-Agent
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
          alert(xhr.responseText);
        } else {
          alert("Request was unsuccessful: " + xhr.status);
        }
      }
    };
    xhr.open('get', 'example.txt', true);
    xhr.send(null);
    

    如果需要发送额外的请求头部,可以使用setRequestHeader()方法。xhr.setRequestHeader('myHeader', 'myValue'),为保证请求头部被发送,必须在open()之后,send()之前调用setRequestHeader()。

    POST请求

    客户端

    let data = new FormData();
    data.append('Name', 'Mr.Yao');
    xhr.open('post', 'http://127.0.0.1:3000')
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send(data)
    

    服务端

    var http = require('http');
    var querystring = require('querystring');
    var util = require('util');
     
    http.createServer(function(req, res){
        // 定义了一个post变量,用于暂存请求体的信息
        res.setHeader("Access-Control-Allow-Origin", "*"); 
        var post = '';     
     
        // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
        req.on('data', function(chunk){    
            post += chunk;
        });
        // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
        req.on('end', function(){    
            post = querystring.parse(post);
            res.end(util.inspect(post));
        });
    }).listen(3000);
    

    超时

    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        try {
          if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
            alert(xhr.responseText);
          } else {
            alert('Request was unsuccessful: ' + xhr.status);
          }
        } catch(ex) {
          // 假设由ontimeout处理
        }
      }
    }
    xhr.timeout = 1000;
    xhr.ontimeout = function() {
      alert('Request did not return in a second');
    };
    

    给timeout设置1000毫秒,如果请求没有在1秒钟内返回则会中断。此时则会触发ontimeout事件处理程序,readyState仍会变成4,因此也会调用onreadystatechange事件处理程序。不过,如果在超时之后访问status属性则会发生错误。

    let xhr = new XMLHttpRequest();
    xhr.open('get', 'text.php', true);
    xhr.overrideMimeType('text/xml');
    xhr.send(null);
    

    进度事件

    load事件在响应接收完成后立即触发,这样就不用检查readyState属性了。onload事件处理程序会收到一个event对象,其target属性设置为XHR实例,在这个实例上可以访问所有XHR对象属性和方法。

    let xhr = new XMLHttpRequest();
    xhr.onload = function() {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
        alert(xhr.responseText);
      } else {
        alert("Request was unsuccessful: " + xhr.status);
      }
    };
    xhr.open("get", "altevents.php", true);
    xhr.send(null);
    

    progress事件在浏览器接收数据期间,这个事件会反复触发。

    let xhr = new XMLHttpRequest();
    xhr.onload = function(event) {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
        alert(xhr.responseText);
      } else {
        alert("Request was unsuccessful: " + xhr.status);
      }
    };
    xhr.onprogress = function(event) {
    let divStatus = document.getElementById("status");
    if (event.lengthComputable) { 21
        divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes"; }
    };
    xhr.open("get", "altevents.php", true);
    xhr.send(null);
    

    loadstart:在接收到响应的第一个字节时触发。

    error:在请求出错时触发。

    abort:在调用abort()终止连接时触发。

    每次请求都会首先触发loadstart事件,之后是一个或多个progress事件,接着是error、abort或load中的一个,最后以loadend事件结束。

    跨域资源共享

    跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。 CORS背后的基本思路就是使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。

    对于简单的请求,比如GET或POST请求,没有自定义头部,而且请求体是text/plain类型,这样的请求在发送时会有一个额外的头部叫Origin。Origin头部包含发送请求的页面的源(协议、域名和端口),以便服务器确定是否为其提供响应。如果服务器决定响应请求,那么应该发送Access-Control-Allow-Origin头部,包含相同的源;或者如果资源是公开的,那么就包含"*"。

    图片探测

    图片探测是利用<img>标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,然后通过它们的onload和onerror事件处理程序得知何时收到响应。这种动态创建图片的技术经常用于图片探测(image pings)。图片探测是与服务器之间简单、跨域、单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或值为204的状态码。浏览器通过图片探测拿不到任何数据,但可以通过监听onload和onerror事件知道什么时候能接收 到响应。

    let img = new Image();
    img.onload = img.onerror = function() {
      alert("Done!");
    };
    img.src = "http://www.example.com/test?name=Nicholas";
    

    图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发送GET请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的原因。

    JSONP

    JSONP格式包含两个部分:回调和数据。回调是在页面接收到响应之后应该调用的函数,通常回调函数的名称是通过请求来动态指定的。而数据就是作为参数传给回调函数的JSON数据。

    JSONP调用是通过动态创建<script>元素并为src属性指定跨域URL实现的。此时的<script><img>元素类似,能够不受限制地从其他域加载资源。因为JSONP是有效的JavaScript,所以JSONP响应在被加载完成之后会立即执行。

    function handleResponse(response) {
    	console.log(`You're at IP address ${response.ip}, which is in ${response.city}, ${response.region_name}`);
    }
    let script = document.createElement("script");
    script.src = "http://freegeoip.net/json/?callback=handleResponse";
    document.body.insertBefore(script, document.body.firstChild);
    

    首先,JSONP是从不同的域拉取可执行代码。如果这个域并不可信,则可能在响应中加入恶意内容。此时除了完全删除JSONP没有其他办法。在使用不受控的Web服务时,一定要保证是可以信任的。

    第二个缺点是不好确定JSONP请求是否失败。虽然HTML5规定了<script>元素的onerror事件 处理程序,但还没有被任何浏览器实现。为此,开发者经常使用计时器来决定是否放弃等待响应。这种方式并不准确,毕竟不同用户的网络连接速度和带宽是不一样的。

    Fetch API

    Fetch API能够执行XMLHttpRequest对象的所有任务,但更容易使用,接口也更现代化,能够在Web工作线程等现代Web工具中使用。XMLHttpRequest可以选择异步,而Fetch API则必须是异步。

    分派请求

    fetch()只有一个必需的参数input。多数情况下,这个参数是要获取资源的URL。这个方法返回一个期约。

    fetch('bar.txt')
     	.then((response) => {
      console.log(response);
    });
    // Response { type: "basic", url: ... }
    

    读取响应

    读取响应内容的最简单方式是取得纯文本格式的内容,这要用到text()方法。这个方法返回一个期约,会解决为取得资源的完整内容。

    fetch('bar.txt')
      .then((response) => response.text())
      .then((data) => console.log(data));
    // bar.txt 的内容
    

    处理状态码和请求失败

    fetch('/bar')
      .then((response) => {
      console.log(response.status);     // 200
      console.log(response.statusText); // OK
    });
    

    可以显式地设置fetch()在遇到重定向时的行为,不过默认行为是跟随重定向并返回状态码不是300~399的响应。跟随重定向时,响应对象的redirected属性会被设置为true,而状态码仍然是200。

    自定义选项

    只使用URL时,fetch()会发送GET请求,只包含最低限度的请求头。要进一步配置如何发送请求,需要传入可选的第二个参数init对象。

    • body
      指定使用请求体时请求体的内容,必须是Blob、BufferSource、FormData、URLSearchParams、ReadableStream或String的实例。

    • headers

      用于指定请求头部,必须是Headers对象实例或包含字符串格式键/值对的常规对象,默认值为不包含键/值对的Headers对象。这不意味着请求不包含任何头部,浏览器仍然会随请求发送一些头部。虽然这些头部对JavaScript不可见,但浏览器的网络检查器可以观察到。

    • method

      用于指定HTTP请求方法。

    常见的fetch请求模式

    发送JSON数据

    let payload = JSON.stringify({
    	foo: 'bar'
    });
    let jsonHeaders = new Headers({
    	'Content-Type': 'application/json'
    });
    fetch('/send-me-json', {
    	method: 'POST', // 发送请求体时必须使用一种HTTP方法body: payload,
    	headers: jsonHeaders
    });
    

    在请求体中发送参数

    let payload = 'foo=bar&baz=qux';
    let paramHeaders = new Headers({
    	'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    });
    fetch('/send-me-params', {
    	method:'POST', //发送请求体时必须使用一种HTTP方法body: payload,
    	headers: paramHeaders
    });
    

    发送文件

    let imageFormData = new FormData();
    let imageInput = document.querySelector("input[type='file']");
    imageFormData.append('image', imageInput.files[0]);
    fetch('/img-upload', {
      method: 'POST',
      body: imageFormData
    });
    

    加载Blod文件

    Fetch API也能提供Blob类型的响应,而Blob又可以兼容多种浏览器API。一种常见的做法是明确将图片文件加载到内存,然后将其添加到HTML图片元素。为此,可以使用响应对象上暴露的blob()方法。这个方法返回一个期约,解决为一个Blob的实例。然后,可以将这个实例传给URL.createObjectUrl()以生成可以添加给图片元素src属性的值。

    const imageElement = document.querySelector('img');
    fetch('my-image.png')
    	.then((response) => response.blob())
    	.then((blob) => {
      	imageElement.src = URL.createObjectURL(blob);
     	});
    

    发送跨源请求

    从不同的源请求资源,响应要包含CORS头部才能保证浏览器收到响应。没有这些头部,跨源请求会失败并抛出错误。

    如果代码不需要访问响应,也可以发送no-cors请求。此时响应的type属性值为opaque,因此无法读取响应内容。这种方式适合发送探测请求或者将响应缓存起来供以后使用。

    fetch('//cross-origin.com', { method: 'no-cors' }) 
      .then((response) => console.log(response.type));
    // opaque
    

    中断请求

    Fetch API支持通过AbortController/AbortSignal对中断请求。调用AbortController.abort()会中断所有网络传输,特别适合希望停止传输大型负载的情况。中断进行中的fetch()请求会导致包含错误的拒绝。

    Web Socket

    Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。在JavaScript中创建WebSocket时,一个HTTP请求会发送到服务器以初始化连接。服务器响应后,连接使用HTTP的Upgrade头部从HTTP协议切换到Web Socket协议。这意味着Web Socket不能通过标准HTTP服务器实现,而必须使用支持该协议的专有服务器。因为Web Socket使用了自定义协议,所以URL方案(scheme)稍有变化:不能再使用http://或https://,而要使用ws://和wss://。前者是不安全的连接,后者是安全连接。在指定Web Socket URL时,必须包含URL方案,因为将来有可能再支持其他方案。

    创建

    let socket = new WebSocket("ws://www.example.com/server.php");
    

    浏览器会在初始化WebSocket对象之后立即创建连接。与XHR类似,WebSocket也有一个readyState属性表示当前状态。不过,这个值与XHR中相应的值不一样。

    • WebSocket.OPENING(0):连接正在建立。
    • WebSocket.OPEN(1):连接已经建立。
    • WebSocket.CLOSING(2):连接正在关闭。
    • WebSocket.CLOSE(3):连接已经关闭。

    任何时候都可以调用close()方法关闭Web Socket连接:

    socket.close();
    

    发送和接收数据

    打开Web Socket之后,可以通过连接发送和接收数据。要向服务器发送数据,使用send()方法并 传入一个字符串、ArrayBuffer或Blob。

    服务器向客户端发送消息时,WebSocket对象上会触发message事件。这个message事件与其他消息协议类似,可以通过event.data属性访问到有效载荷。

    其他事件

    WebSocket 对象在连接生命周期中有可能触发3个其他事件。

    • open:在连接成功建立时触发。
    • error:在发生错误时触发。连接无法存续。
    • close:在连接关闭时触发。

    客户端

    var ws = new WebSocket('ws://localhost:3000/');
    // Web Socket 已连接上,使用 send() 方法发送数据
    ws.onopen = function() {
      // 这里用一个延时器模拟事件
      setInterval(function() {
        ws.send('客户端消息');
      },2000);
    }
    // 这里接受服务器端发过来的消息
    ws.onmessage = function(e) {
      console.log(e.data)
    }
    

    服务端

    var ws = require('nodejs-websocket');
    var server = ws.createServer(function(socket){
    // 事件名称为text(读取字符串时,就叫做text),读取客户端传来的字符串
      var count = 1;
      socket.on('text', function(str) {
        // 在控制台输出前端传来的消息  
        console.log(str);
        //向前端回复消息
        socket.sendText('服务器端收到客户端端发来的消息了!' + count++);
      });
      socket.on('error', function(code) {
        // 某些情况如果客户端多次触发连接关闭,会导致connection.close()出现异常,这里try/catch一下
        try {
          socket.close()
        } catch (error) {
          console.log('close异常', error)
        }
        console.log('异常关闭', code)
      })
    }).listen(3000);
    
  • 相关阅读:
    SSM框架整合常见错误
    4楼B座--内心的梦想,需要我们用心去实现
    打鸡血的废话
    聆听小故事
    php algorithm
    面试笔试常考的mysql 数据库操作group by
    linux mysqli extension is missing
    模仿与创新
    聊天室刷屏的简单原理实现
    程序员迷茫的未来
  • 原文地址:https://www.cnblogs.com/1328497946TS/p/15238328.html
Copyright © 2020-2023  润新知