> 웹 프론트엔드 > JS 튜토리얼 > AJAX 원칙 및 CORS 도메인 간 방법

AJAX 원칙 및 CORS 도메인 간 방법

一个新手
풀어 주다: 2017-10-25 14:24:38
원래의
4912명이 탐색했습니다.

ajax는 프론트 엔드 개발에 필요한 기본 기능 중 하나입니다. 사용해 볼 수는 있지만 서버 통신과 관련된 원리와 더 깊은 지식을 반드시 이해할 수는 없습니다. 지난 이틀 동안 정리하는 동안 많은 기사를 읽고 내 백엔드 역량으로 인해 네트워크 통신 관련 지식 영역을 탐색하는 데 제한이 있다는 것을 알게 되었으며, 가능한 한 빨리 부족한 점을 보완해야 합니다.

xhr/xdr/ajax/cors/http의 일부를 포함하여 ajax 관련 항목에 대해 이야기해 보겠습니다. 이는 IE6/7과 같이 더 이상 사용되지 않는 기록 Baggage를 삭제합니다. 등. ajax相关的东西,包括xhr/xdr/ajax/cors/http的一部分内容,其中会抛弃一些被弃用的历史包袱,如IE6/7等。

Ajax的出现

2005年,Jesse James Garrett提出了Ajax的技术,其全称为Asynchronous Javascript and XML,Ajax的核心是XMLHttpRequest对象,简称XHR,它用于使浏览器向服务器请求额外的数据而不卸载页面,极大的提高了用户体验。在此之前,其实这种技术已经存在并被一些人实现,但并没有流行也没有被浏览器支持。不过在此之后,IE5第一次引入XHR对象,并支持ajax技术,后续被所有浏览器支持。

XMLHttpRequest对象和请求

XHR是一个API,为客户端提供服务端和客户端之间通信的功能,并且不会刷新页面。它并不仅仅能取回XML类型的数据,而能取回所有类型的数据,除了http协议,还支持file和ftp协议。我们可以通过其构造函数来创建一个新的XHR对象,这个操作需要在其它所有操作之前完成:

var xhr = new XMLHttpRequest();
로그인 후 복사

通过控制台我们可以很方便看到XHR的原型链:Object -> EventTarget -> XMLHttpRequestEventTarget -> XMLHttpRequest。它拥有原型链上和本身的方法和属性,现在看下我们常用的方法:
AJAX 원칙 및 CORS 도메인 간 방법

我们解释下它的几个主要方法,我们在创建了新的xhr对象之后,首先要调用它的open()方法:

// 第一个参数可以为get/post等,表示该请求的类型
// 第二个参数是请求的url,可以为相对路径或绝对路径
// 第三个参数代表是否异步,为true时异步,为false时同步
// 第四五个参数为可选的授权使用的参数,因为安全性不推荐明文使用
xhr.open('get', 'example.php', true, username, password);
로그인 후 복사

在这里受同源策略的影响,当第二个参数url跨域的时候会被浏览器报安全错误。同源策略指的是当前页面和目标url协议、域名和端口均相同。后面也会讲到,除IE之外的浏览器通过XHR对象实现跨域请求,只需将url设置为绝对url即可。

当初始化请求完成后,我们调用send()方法发送请求:

var data = new FormData();
data.append('name', 'Nicholas');
// 接受一个请求主体发送的数据,如果不需要,传入null
xhr.send(data);
로그인 후 복사

当请求的类型为get/head时,send()的参数会被忽略并置为null,send()传递的参数会影响到我们请求的头部content-type的默认值,该字段代表返回的资源内容的类型,用于浏览器处理,如果没有设置或在一些场景下,浏览器会进行MIME嗅探来确定怎么处理返回的资源。

XHR2级中定义了FormData数据,用于常见的类表单数据序列化:

// 直接传入表单id
var data = new FormData(document.getElementById('user-form'));

// 创建类表单数据
var data = new FormData();
data.append('name', 'Nicholas');

// `FormData`可以直接被send()调用,会自动修改xhr的content-type头部
xhr.send(data);

// 请求头部的content-type: multipart/form-data; boundary=----WebKitFormBoundaryjn3q2KKRYrEH55Vz

// 请求的上传数据 Request Payload:
------WebKitFormBoundaryjn3q2KKRYrEH55Vz
Content-Disposition: form-data; name="name"

Nicholas
------WebKitFormBoundaryjn3q2KKRYrEH55Vz--
로그인 후 복사

FormData常用的方法有append/delete/entries/forEach/get/getAll/has/keys/set/values,都是常用的跟数组类似的方法,不再解释。

请求方法

GET是最常见的请求类型,可以将查询字符串参数添加到URL尾部,对XHR而言,该查询字符串必须经过正确编码,每个键值对必须使用encodeURIComponent()进行编码,键值对之间由&分割:

// 封装序列化键值对
function addURLParam(url, name, value) {
    url += (url.indexOf('?') === -1 ? '?' : '&';
    url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
    return url;
}
로그인 후 복사

POST请求使用频率仅次于GET请求,通常发送较多数据,且格式不限,数据传递给send()作为参数。

HTTP一共规定了九种请求方法,每一个动词代表不同的语义,但是常用的只有上面两种:

 - OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。 
 - HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。 
 - GET:向特定的资源发出请求。 
 - POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。 
 - PUT:向指定资源位置上传其最新内容。 
 - DELETE:请求服务器删除Request-URI所标识的资源。 
 - TRACE:回显服务器收到的请求,主要用于测试或诊断。 
 - CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
 - PATCH: 用于对资源进行部分修改
로그인 후 복사

HTTP头部信息

每个HTTP请求和响应都带有头部信息,xhr对象允许我们操作部分头部信息。我们可以通过xhr.setRequestHeader()方法来设置自定义的头部信息或者修改浏览器默认的正常头部信息。常用的请求头部:

// 下面的实例是从我本地的一次请求取出的

Accept: 浏览器能够处理的内容类型。// */*
Accept-Charset: 浏览器能够显示的字符集。// 未取到
Accept-Encoding: 浏览器能够处理的压缩编码。// gzip,deflate
Accept-Language: 浏览器当前设置的语言。// zh-CN,zh;q=0.8,en;q=0.6
Connection: 浏览器与服务器之间连接的类型。// keep-alive
Cookie: 当前页面设置的任意Cookie。// JlogDataSource=jomodb
Host: 发出请求的页面所在域。// gzhxy-cdn-oss-06.gzhxy.baidu.com:8090
Referer: 发出请求的页面URI。// http://gzhxy-cdn-oss-06.gzhxy.baidu.com:8090/jomocha/index.php?r=tools/offline/index
User-Agent: 浏览器的用户代理字符串。// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
로그인 후 복사

我们一般不修改浏览器正常的头部信息,可能会影响到服务器响应。如果需要可以通过xhr.setRequestHeader()进行修改:

// 传入头部键值对,键值不区分大小写,如果多次设置,则追加
// 此时请求头部的content-type: application/json, text/html
xhr.setRequestHeader('content-type', 'application/json');
xhr.setRequestHeader('content-type', 'application/json');
로그인 후 복사

设置头部信息需要在open()之后,send()之前进行调用。响应的头部信息在后端处理,不在此处讲解。有一部分请求头部信息不允许设置,如Accept-Encoding, Cookie等。

在请求返回后,我们可以获取到响应头部:

// 获取指定项的响应头
xhr.getResponseHeader('content-type'); // application/json;charset=utf-8
// 获取所有的响应头部信息
xhr.getAllResponseHeaders();
로그인 후 복사

这里简单说下content-type值,指的是请求和响应的HTTP内容类型,影响到服务器和浏览器对数据的处理方式,默认为text/html

Ajax의 등장

2005년 Jesse James GarrettAsynchronous Javascript and XML이라 불리는 Ajax 기술을 제안했습니다. XHR라고 하는 XMLHttpRequest 개체는 브라우저가 페이지를 언로드하지 않고도 서버에 추가 데이터를 요청할 수 있도록 하는 데 사용되어 사용자 경험을 크게 향상시킵니다. 이전에는 이 기술이 실제로 존재하고 일부 사람들에 의해 구현되었지만 대중적이지 않았고 브라우저에서 지원하지 않았습니다. 하지만 이후 IE5에서는 처음으로 XHR 객체를 도입하고 ajax 기술을 지원했고, 이후 모든 브라우저에서 지원하게 되었습니다. 🎜

XMLHttpRequest객체 및 요청

🎜XHR은 서버와 클라이언트 간의 통신 기능을 클라이언트에 제공하는 API이며, 클라이언트에 페이지를 새로 고칩니다. XML 유형의 데이터를 검색할 수 있을 뿐만 아니라 모든 유형의 데이터를 검색할 수 있으며 http 프로토콜 외에도 파일 및 ftp 프로토콜도 지원합니다. 생성자를 통해 새로운 XHR 객체를 생성할 수 있습니다. 이 작업은 다른 모든 작업보다 먼저 완료되어야 합니다. 🎜
// 包含资源类型,字符编码, 边界字符串三个参数,可选填

text/html;charset=utf-8    // html标签文本
text/plain    // 纯文本
text/css    // css文件
text/javascript    // js文件
// 普通的表单数据,可以通过表单标签的enctype属性指定
application/x-www-form-urlencode
// 发送文件的POST包,包过大需要分片时使用`boundary`属性分割数据作边界
multipart/form-data; boundary=something
// json数据格式
application/json
// xml类型的标记语言
application/xml
로그인 후 복사
로그인 후 복사
🎜콘솔 >프로토타입 체인을 통해 XHR</code를 쉽게 볼 수 있습니다. <code>객체 -> EventTarget -> XMLHttpRequest. 프로토타입 체인과 자체에 메서드와 속성이 있습니다. 이제 일반적으로 사용되는 메서드를 살펴보겠습니다.
AJAX 원칙 및 CORS 도메인 간 방법🎜🎜 몇 가지 주요 메소드를 설명하겠습니다. 새 xhr 객체를 만든 후 먼저 를 호출해야 합니다. open()메서드: 🎜
// xhr v1 的写法,检测readystate的值,为4则说明数据准备完毕,需要在open()前定义
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200 || xhr.status === 304) {
            console.log(xhr.responseText);
        } else {
            console.log(xhr.statusText);
        }
    }   
}

// xhr v2 的写法,onload()事件说明数据准备完毕
xhr.onload = function () {
    if (xhr.status === 200 || xhr.status === 304) {
        console.log(xhr.responseText);
    } else {
        console.log(xhr.statusText);
    }
}
로그인 후 복사
로그인 후 복사
🎜여기서 동일 출처 정책의 영향을 받으며, 두 번째 매개변수 URL이 크로스 도메인인 경우 브라우저에서 보안 오류가 보고됩니다. 동일 출처 정책은 현재 페이지와 대상 URL의 프로토콜, 도메인 이름, 포트가 동일함을 의미합니다. 나중에 언급하겠지만 IE 이외의 브라우저는 XHR 객체를 통해 도메인 간 요청을 구현하므로 URL을 절대 URL로 설정하기만 하면 됩니다. 🎜🎜초기화 요청이 완료된 후 send() 메서드를 호출하여 요청을 보냅니다. 🎜
response    // 响应的数据
responseURL    // 发起响应的URL
responseType    // 响应的类型,用于浏览器强行重置响应数据的类型
responseText    // 如果为普通文本,则在这显示
responseXML    // 如果为xml类型文本,在这里显示
로그인 후 복사
로그인 후 복사
🎜요청 유형이 get/head인 경우 매개변수는 send()는 무시되고 null로 설정됩니다. send()에 의해 전달된 매개변수는 요청 헤더 content-type의 기본값에 영향을 미칩니다. 이 필드는 반환된 리소스 콘텐츠의 유형을 나타내며 다음에 사용됩니다. 브라우저 처리. 설정되지 않은 경우 또는 일부 시나리오에서는 브라우저가 MIME 스니핑을 수행하여 반환된 리소스를 처리하는 방법을 결정합니다. 🎜🎜FormData 데이터는 일반 양식 데이터 직렬화에 사용되는 XHR2 레벨에 정의됩니다. 🎜
// xhr v1 的写法,设置响应资源的处理类型
xhr.overrideMimeType('text/xml');

// xhr v2 的写法, 可用值为 arraybuffer/blob/document/json/text
xhr.responseType = 'document';
로그인 후 복사
로그인 후 복사
🎜FormData 일반적으로 사용되는 방법은 append/delete/entries/forEach/get/getAll/has/keys/set/values는 배열과 유사하게 일반적으로 사용되는 메서드이므로 다시 설명하지 않습니다. 🎜

요청 방법

🎜GET은 가장 일반적인 요청 유형입니다. 쿼리 문자열 매개변수는 URL 끝에 추가할 수 있습니다. XHR의 경우 쿼리 문자열을 올바르게 인코딩해야 합니다. , 각 키-값 쌍은 encodeURIComponent()를 사용하여 인코딩되어야 하며 키-값 쌍은 &로 구분됩니다. 🎜
xhr.timeout = 1000;    // 1分钟,单位为ms
xhr.ontimeout = function () {};
로그인 후 복사
로그인 후 복사
🎜POST 요청 빈도는 GET 요청에 이어 두 번째입니다. 일반적으로 데이터는 send()에 매개변수로 전달됩니다. 🎜🎜HTTP는 총 9가지 요청 방법을 제공합니다. 각 동사는 서로 다른 의미를 나타내지만 일반적으로 사용되는 두 가지 방법은 다음과 같습니다. 🎜
xhr.onprogress = function (event) {
    if (event.lengthComputable) {
        console.log(event.loaded / event.total);
    }
}
로그인 후 복사
로그인 후 복사

HTTP 헤더 정보

🎜각 HTTP 요청 및 응답에는 헤더 정보가 포함됩니다. xhr 객체를 사용하면 일부 헤더 정보를 조작할 수 있습니다. xhr.setRequestHeader() 메소드를 통해 커스텀 헤더 정보를 설정하거나 브라우저의 기본 일반 헤더 정보를 수정할 수 있습니다. 일반적으로 사용되는 요청 헤더: 🎜
Origin: http://www.baidu.com    // 浏览器的头部信息

// 如果服务端认可这个域名的跨域请求,如下设置就可跨域访问资源
Access-Control-Allow-Origin: http://www.baidu.com
로그인 후 복사
로그인 후 복사
🎜저희는 일반적으로 서버 응답에 영향을 미칠 수 있는 일반 브라우저 헤더 정보를 수정하지 않습니다. 필요한 경우 xhr.setRequestHeader()를 통해 수정할 수 있습니다. 🎜
xhr.withCredentials = true;    // 浏览器端
Access-Control-Allow-Credentials: true;    // 服务端
로그인 후 복사
로그인 후 복사
🎜 헤더 정보 설정은 open(), send() 이후에 수행해야 합니다. 전화를 걸기 전에. 응답 헤더 정보는 백엔드에서 처리되므로 여기서는 설명하지 않습니다. Accept-Encoding, Cookie 등과 같은 일부 요청 헤더 정보는 설정이 허용되지 않습니다. 🎜🎜요청이 반환된 후 응답 헤더를 얻을 수 있습니다: 🎜
var xdr = new XDomainRequest();
xdr.open('get', 'http://www.site.com/page');
xdr.send(null);
로그인 후 복사
로그인 후 복사
🎜 다음은 서버와 브라우저의 방식에 영향을 미치는 요청 및 응답의 HTTP 콘텐츠 유형을 나타내는 content-type 값에 대한 간략한 설명입니다. 프로세스 데이터입니다. 기본값은 text/html이며 일반적으로 사용되는 항목은 다음과 같습니다. 🎜
// 包含资源类型,字符编码, 边界字符串三个参数,可选填

text/html;charset=utf-8    // html标签文本
text/plain    // 纯文本
text/css    // css文件
text/javascript    // js文件
// 普通的表单数据,可以通过表单标签的enctype属性指定
application/x-www-form-urlencode
// 发送文件的POST包,包过大需要分片时使用`boundary`属性分割数据作边界
multipart/form-data; boundary=something
// json数据格式
application/json
// xml类型的标记语言
application/xml
로그인 후 복사
로그인 후 복사

XHR对象的响应

我们现在对请求的发起很了解了,接着看下如何拿到响应数据。如果我们给open()传递的第三个参数是true,则代表为同步请求,那么js会被阻塞直到拿到响应,而如果为false则是异步请求,我们只需要绑定xhr.onreadystatechange()事件监听响应即可。最上面的图已经说明了readystate的值含义,所以我们可以:

// xhr v1 的写法,检测readystate的值,为4则说明数据准备完毕,需要在open()前定义
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200 || xhr.status === 304) {
            console.log(xhr.responseText);
        } else {
            console.log(xhr.statusText);
        }
    }   
}

// xhr v2 的写法,onload()事件说明数据准备完毕
xhr.onload = function () {
    if (xhr.status === 200 || xhr.status === 304) {
        console.log(xhr.responseText);
    } else {
        console.log(xhr.statusText);
    }
}
로그인 후 복사
로그인 후 복사

xhr对象的响应数据中包含几个属性:

response    // 响应的数据
responseURL    // 发起响应的URL
responseType    // 响应的类型,用于浏览器强行重置响应数据的类型
responseText    // 如果为普通文本,则在这显示
responseXML    // 如果为xml类型文本,在这里显示
로그인 후 복사
로그인 후 복사

数据会出现在responseText/responseXML中的哪一个,取决于服务器返回的MIME类型,当然我们也有一些方式在浏览器端设置如何处理这些数据:

// xhr v1 的写法,设置响应资源的处理类型
xhr.overrideMimeType('text/xml');

// xhr v2 的写法, 可用值为 arraybuffer/blob/document/json/text
xhr.responseType = 'document';
로그인 후 복사
로그인 후 복사

响应数据相关的属性默认为null / '',只有当请求完成并被正确解析的时候才会有值,取决于responseType的值,来确定response/responseText/responseXML谁最终具有值。

XHR的高级功能

xhr v2里提供了超时和进度事件。

超时

xhr.timeout = 1000;    // 1分钟,单位为ms
xhr.ontimeout = function () {};
로그인 후 복사
로그인 후 복사

在请求send()之后开始计时,等待timeout时长后,如果没有收到响应,则触发ontimeout()事件,超时会将readystate=4,直接触发onreadystatechange()事件。

请求进度

像上图所示,xhr v2定义了不同的进度事件:loadstart/progress/error/abort/load/loadend,这其中我们已经说过了onload()事件为内容加载完成可用。现在说一下onprogress()进度事件:

xhr.onprogress = function (event) {
    if (event.lengthComputable) {
        console.log(event.loaded / event.total);
    }
}
로그인 후 복사
로그인 후 복사

该事件会接收一个event对象,其target属性为该xhr对象,lengthComputable属性为total size是否已知,即是否可用进度信息,loaded属性为已经接收的字节数,total为总字节数。该事件会在数据接收期间不断触发,但间隔不确定。

跨域CORS

提到XHR对象,我们就会讲到跨域问题,它是为了预防某些恶意行为的安全策略,但有时候我们需要跨域来实现某些功能。需要注意的是跨域并不仅仅是前端单方面的事情,它需要后端代码进行配合,我们只是通过一些方式跳过了浏览器的阻拦。

对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

CORS(Cross-Origin Resource Sharing, 跨域资源共享)的思想是浏览器和服务端通过头部信息来进行沟通确认是否给予响应。如:

Origin: http://www.baidu.com    // 浏览器的头部信息

// 如果服务端认可这个域名的跨域请求,如下设置就可跨域访问资源
Access-Control-Allow-Origin: http://www.baidu.com
로그인 후 복사
로그인 후 복사

如上就可以实现最简单的跨域访问,但是此时不能携带任何的cookie,如果我们需要传递cookie进行身份认证,需要设置:

xhr.withCredentials = true;    // 浏览器端
Access-Control-Allow-Credentials: true;    // 服务端
로그인 후 복사
로그인 후 복사

这样我们就可以传递认证信息了,但如果允许认证,Access-Control-Allow-Origin不能设置为*,而一定是具体的域名信息。

现在的浏览器都对CORS有了实现,如IE使用XDomainRequest对象,其它浏览器使用XMLHttpRequest对象。所以在此之前有很多奇技淫巧,如通过jsonp/图像 Ping方法都不再详述,而且其都需要服务端配合并且有很多局限性。

IE实现: XDomainRequest

var xdr = new XDomainRequest();
xdr.open('get', 'http://www.site.com/page');
xdr.send(null);
로그인 후 복사
로그인 후 복사

XDR区别于普通XHR:

  • 不能传输cookie

  • 只能设置请求头部的content-type

  • 不能访问响应头部信息

  • 只支持get/post方法

通过这些区别可以阻止一部分的CSRF(Cross-Site Request Forgery,跨站点请求伪造)XSS(Cross-Site Scripting,跨站点脚本)

XDR与XHR的使用上非常相似,区别有几点:

  • open()方法只接受两个参数,请求类型和URL

  • 只允许异步请求

  • 响应完成触发onload()事件,但我们只能访问responseText原始文本,并且无法获取响应的status.

  • 异常事件都会触发error事件,并且无错误信息可用。

其余浏览器实现: XMLHttpRequest

其余浏览器通过XHR对象直接实现了CORS,你只需要做的就是open()方法中传入一个绝对URL。

xhr.open('get', 'http://www.site.com/page', true);
로그인 후 복사

相对于普通的XHR对象,CORS-XHR依然有部分限制:

  • 不能使用setRequestHeader()定义头部

  • 不能传递cookie

  • 调用getAllResponseHeaders(),结果为空

其余跨域方法

上面的两种方法已经很成熟了,但是仍然有一部分方法可以跨域,比如图像Ping

var img = new Image();
img.onload = img.onerror = function () {
    console.log('done');
}
img.src = 'http://www.site.com/test?name=Nicholas';
로그인 후 복사

这种方式常用于服务端统计广告的点击次数,其缺陷为:

  • 只能是GET请求

  • 单向通信,无法获取响应文本

另外还有JSONP

function handleResponse(response) {
    console.log(response.ip, response.city);
}

var script = document.createElement('script');
script.src = 'http://freegeoip.net/json?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
로그인 후 복사

这种方式通过和服务器配合,跨域请求一个js文件并被服务器处理后传回:

handleResponse({'name': 'Nicholas'});
로그인 후 복사

然后直接在浏览器调用了该函数,传回的数据被当做response形参进行处理。但它也有一些缺陷:

  • 访问的方式是请求js,所以如果域名不安全,则很容易被恶意代码直接执行并攻击

  • 无法检测是否错误,因为js不支持这样的接口事件,只能超时判断

上面两种方式很容易看出,我们在支持CORS之前,使用的方法只不过是采用img/css/js等不受跨域访问限制的对象,变相拿到了响应数据,但都有缺陷,所以如果没有历史包袱,建议采用XDR或XHR对象来实现跨域访问。

XHR的兼容性

我们可以直接到Can I use这个网站上查询兼容性问题:
AJAX 원칙 및 CORS 도메인 간 방법


위 내용은 AJAX 원칙 및 CORS 도메인 간 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿