`

xmlhttprequest obj

    博客分类:
  • js
阅读更多
XMLHttpRequest对象是当今所有AJAX和Web 2.0应用程序的技术基础。尽管软件经销商和开源社团现在都在提供各种AJAX框架以进一步简化XMLHttpRequest对象的使用;但是,我们仍然很有必要理解这个对象的详细工作机制。

  一、 引言

  异步JavaScript与XML(AJAX)是一个专用术语,用于实现在客户端脚本与服务器之间的数据交互过程。这一技术的优点在于,它向开发者提供了一种从Web服务器检索数据而不必把用户当前正在观察的页面回馈给服务器。与现代浏览器的通过存取浏览器DOM结构的编程代码(JavaScript) 动态地改变被显示内容的支持相配合,AJAX让开发者在浏览器端更新被显示的HTML内容而不必刷新页面。换句话说,AJAX可以使基于浏览器的应用程序更具交互性而且更类似传统型桌面应用程序。

  Google的Gmail和Outlook Express就是两个使用AJAX技术的我们所熟悉的例子。而且,AJAX可以用于任何客户端脚本语言中,这包括JavaScript,Jscript和VBScript。

  AJAX利用一个构建到所有现代浏览器内部的对象-XMLHttpRequest-来实现发送和接收HTTP请求与响应信息。一个经由 XMLHttpRequest对象发送的HTTP请求并不要求页面中拥有或回寄一个<form>元素。AJAX中的"A"代表了"异步",这意味着 XMLHttpRequest对象的send()方法可以立即返回,从而让Web页面上的其它HTML/JavaScript继续其浏览器端处理而由服务器处理HTTP请求并发送响应。尽管缺省情况下请求是异步进行的,但是,你可以选择发送同步请求,这将会暂停其它Web页面的处理,直到该页面接收到服务器的响应为止。

  微软在其Internet Explorer(IE) 5中作为一个ActiveX对象形式引入了XMLHttpRequest对象。其他的认识到这一对象重要性的浏览器制造商也都纷纷在他们的浏览器内实现了 XMLHttpRequest对象,但是作为一个本地JavaScript对象而不是作为一个ActiveX对象实现。而如今,在认识到实现这一类型的价值及安全性特征之后,微软已经在其IE 7中把XMLHttpRequest实现为一个窗口对象属性。幸运的是,尽管其实现(因而也影响到调用方式)细节不同,但是,所有的浏览器实现都具有类似的功能,并且实质上是相同方法。目前,W3C组织正在努力进行XMLHttpRequest对象的标准化,并且已经发行了有关该W3C规范的一个草案。

  本文将对XMLHttpRequest对象API进行详细讨论,并将解释其所有的属性和方法。

  二、 XMLHttpRequest对象的属性和事件

  XMLHttpRequest对象暴露各种属性、方法和事件以便于脚本处理和控制HTTP请求与响应。下面,我们将对此展开详细的讨论。
readyState属性

  当XMLHttpRequest对象把一个HTTP请求发送到服务器时将经历若干种状态:一直等待直到请求被处理;然后,它才接收一个响应。这样以来,脚本才正确响应各种状态-XMLHttpRequest对象暴露一个描述对象的当前状态的readyState属性,如表格1所示。

  表格1.XMLHttpRequest对象的ReadyState属性值列表。
ReadyState取值 描述
0
描述一种"未初始化"状态;此时,已经创建一个XMLHttpRequest对象,但是还没有初始化。
1
描述一种"发送"状态;此时,代码已经调用了XMLHttpRequest open()方法并且XMLHttpRequest已经准备好把一个请求发送到服务器。
2
描述一种"发送"状态;此时,已经通过send()方法把一个请求发送到服务器端,但是还没有收到一个响应。
3
描述一种"正在接收"状态;此时,已经接收到HTTP响应头部信息,但是消息体部分还没有完全接收结束。
4
描述一种"已加载"状态;此时,响应已经被完全接收。


  onreadystatechange事件

  无论readyState值何时发生改变,XMLHttpRequest对象都会激发一个readystatechange事件。其中,onreadystatechange属性接收一个 EventListener值-向该方法指示无论readyState值何时发生改变,该对象都将激活。

  responseText属性

  这个responseText属性包含客户端接收到的HTTP响应的文本内容。当readyState值为0、1或2时,responseText包含一个空字符串。当readyState值为3(正在接收)时,响应中包含客户端还未完成的响应信息。当readyState为4(已加载)时,该 responseText包含完整的响应信息。

  responseXML属性

  此responseXML属性用于当接收到完整的HTTP响应时(readyState为4)描述XML响应;此时,Content-Type头部指定MIME(媒体)类型为 text/xml,application/xml或以+xml结尾。如果Content-Type头部并不包含这些媒体类型之一,那么 responseXML的值为null。无论何时,只要readyState值不为4,那么该responseXML的值也为null。

  其实,这个responseXML属性值是一个文档接口类型的对象,用来描述被分析的文档。如果文档不能被分析(例如,如果文档不是良构的或不支持文档相应的字符编码),那么responseXML的值将为null。

  status属性

  这个status属性描述了HTTP状态代码,而且其类型为short。而且,仅当readyState值为3(正在接收中)或4(已加载)时,这个status属性才可用。当readyState的值小于3时试图存取status的值将引发一个异常。

  statusText属性

  这个statusText属性描述了HTTP状态代码文本;并且仅当readyState值为3或4才可用。当readyState为其它值时试图存取statusText属性将引发一个异常。

 三、 XMLHttpRequest对象的方法

  XMLHttpRequest对象提供了各种方法用于初始化和处理HTTP请求,下列将逐个展开详细讨论。

  abort()方法

  你可以使用这个abort()方法来暂停与一个XMLHttpRequest对象相联系的HTTP请求,从而把该对象复位到未初始化状态。

  open()方法

  你需要调用open(DOMString method,DOMString uri,boolean async,DOMString username,DOMString password)方法初始化一个XMLHttpRequest对象。其中,method参数是必须提供的-用于指定你想用来发送请求的HTTP方法 (GET,POST,PUT,DELETE或HEAD)。为了把数据发送到服务器,应该使用POST方法;为了从服务器端检索数据,应该使用GET方法。另外,uri参数用于指定XMLHttpRequest对象把请求发送到的服务器相应的URI。借助于window.document.baseURI属性,该uri被解析为一个绝对的URI-换句话说,你可以使用相对的URI-它将使用与浏览器解析相对的URI一样的方式被解析。async参数指定是否请求是异步的-缺省值为true。为了发送一个同步请求,需要把这个参数设置为false。对于要求认证的服务器,你可以提供可选的用户名和口令参数。在调用open()方法后,XMLHttpRequest对象把它的readyState属性设置为1(打开)并且把responseText、 responseXML、status和statusText属性复位到它们的初始值。另外,它还复位请求头部。注意,如果你调用open()方法并且此时readyState为4,则XMLHttpRequest对象将复位这些值。

  send()方法

  在通过调用 open()方法准备好一个请求之后,你需要把该请求发送到服务器。仅当readyState值为1时,你才可以调用send()方法;否则的话,XMLHttpRequest对象将引发一个异常。该请求被使用提供给open()方法的参数发送到服务器。当async参数为true 时,send()方法立即返回,从而允许其它客户端脚本处理继续。在调用send()方法后,XMLHttpRequest对象把readyState的值设置为2(发送)。当服务器响应时,在接收消息体之前,如果存在任何消息体的话,XMLHttpRequest对象将把readyState设置为 3(正在接收中)。当请求完成加载时,它把readyState设置为4(已加载)。对于一个HEAD类型的请求,它将在把readyState值设置为 3后再立即把它设置为4。

  send()方法使用一个可选的参数-该参数可以包含可变类型的数据。典型地,你使用它并通过POST方法把数据发送到服务器。另外,你可以显式地使用null参数调用send()方法,这与不用参数调用它一样。对于大多数其它的数据类型,在调用send() 方法之前,应该使用setRequestHeader()方法(见后面的解释)先设置Content-Type头部。如果在send(data)方法中的 data参数的类型为DOMString,那么,数据将被编码为UTF-8。如果数据是Document类型,那么将使用由 data.xmlEncoding指定的编码串行化该数据。

  setRequestHeader()方法

  该setRequestHeader(DOMString header,DOMString value)方法用来设置请求的头部信息。当readyState值为1时,你可以在调用open()方法后调用这个方法;否则,你将得到一个异常。

  getResponseHeader()方法

  getResponseHeader(DOMString header,value)方法用于检索响应的头部值。仅当readyState值是3或4(换句话说,在响应头部可用以后)时,才可以调用这个方法;否则,该方法返回一个空字符串。

  getAllResponseHeaders()方法

  该getAllResponseHeaders()方法以一个字符串形式返回所有的响应头部(每一个头部占单独的一行)。如果readyState的值不是3或4,则该方法返回null。
四、 发送请求

  在AJAX中,许多使用XMLHttpRequest的请求都是从一个HTML事件(例如一个调用JavaScript函数的按钮点击 (onclick)或一个按键(onkeypress))中被初始化的。AJAX支持包括表单校验在内的各种应用程序。有时,在填充表单的其它内容之前要求校验一个唯一的表单域。例如要求使用一个唯一的UserID来注册表单。如果不是使用AJAX技术来校验这个UserID域,那么整个表单都必须被填充和提交。如果该UserID不是有效的,这个表单必须被重新提交。例如,一个相应于一个要求必须在服务器端进行校验的Catalog ID的表单域可能按下列形式指定:
<form name="validationForm" action="validateForm" method="post">
<table>
 <tr><td>Catalog Id:</td>
  <td>
   <input type="text" size="20" id="catalogId" name="catalogId" autocomplete="off" onkeyup="sendRequest()">
  </td>
  <td><div id="validationMessage"></div></td>
 </tr>
</table></form>


  前面的HTML使用validationMessage div来显示相应于这个输入域Catalog Id的一个校验消息。onkeyup事件调用一个JavaScript sendRequest()函数。这个sendRequest()函数创建一个XMLHttpRequest对象。创建一个XMLHttpRequest 对象的过程因浏览器实现的不同而有所区别。如果浏览器支持XMLHttpRequest对象作为一个窗口属性(所有普通的浏览器都是这样的,除了IE 5和IE 6之外),那么,代码可以调用XMLHttpRequest的构造器。如果浏览器把XMLHttpRequest对象实现为一个 ActiveXObject对象(就象在IE 5和IE 6中一样),那么,代码可以使用ActiveXObject的构造器。下面的函数将调用一个init()函数,它负责检查并决定要使用的适当的创建方法- 在创建和返回对象之前。
<script type="text/javascript">
function sendRequest(){
 var xmlHttpReq=init();
 function init(){
  if (window.XMLHttpRequest) {
   return new XMLHttpRequest();
  }
 else if (window.ActiveXObject) {
  return new ActiveXObject("Microsoft.XMLHTTP");
 }
}
</script>


  接下来,你需要使用Open()方法初始化XMLHttpRequest对象-指定HTTP方法和要使用的服务器URL。
var catalogId=encodeURIComponent(document.getElementById("catalogId").value);
xmlHttpReq.open("GET", "validateForm?catalogId=" + catalogId, true);


  默认情况下,使用XMLHttpRequest发送的HTTP请求是异步进行的,但是你可以显式地把async参数设置为true,如上面所展示的。
在这种情况下,对URL validateForm的调用将激活服务器端的一个servlet,但是你应该能够注意到服务器端技术不是根本性的;实际上,该URL可能是一个 ASP,ASP.NET或PHP页面或一个Web服务-这无关紧要,只要该页面能够返回一个响应-指示CatalogID值是否是有效的-即可。因为你在作一个异步调用,所以你需要注册一个XMLHttpRequest对象将调用的回调事件处理器-当它的readyState值改变时调用。记住,readyState值的改变将会激发一个readystatechange事件。你可以使用onreadystatechange属性来注册该回调事件处理器。
xmlHttpReq.onreadystatechange=processRequest;


  然后,我们需要使用send()方法发送该请求。因为这个请求使用的是HTTP GET方法,所以,你可以在不指定参数或使用null参数的情况下调用send()方法。
xmlHttpReq.send(null);





五、 处理请求

  在这个示例中,因为HTTP方法是GET,所以在服务器端的接收servlet将调用一个doGet()方法,该方法将检索在URL中指定的catalogId参数值,并且从一个数据库中检查它的有效性。

  本文示例中的这个servlet需要构造一个发送到客户端的响应;而且,这个示例返回的是XML类型,因此,它把响应的HTTP内容类型设置为 text/xml并且把Cache-Control头部设置为no-cache。设置Cache-Control头部可以阻止浏览器简单地从缓存中重载页面。
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
 ...
 ...
 response.setContentType("text/xml");
 response.setHeader("Cache-Control", "no-cache");
}


  来自于服务器端的响应是一个XML DOM对象,此对象将创建一个XML字符串-其中包含要在客户端进行处理的指令。另外,该XML字符串必须有一个根元素。
out.println("<catalogId>valid</catalogId>");


  【注意】XMLHttpRequest对象的设计目的是为了处理由普通文本或XML组成的响应;但是,一个响应也可能是另外一种类型,如果用户代理(UA)支持这种内容类型的话。

  当请求状态改变时,XMLHttpRequest对象调用使用onreadystatechange注册的事件处理器。因此,在处理该响应之前,你的事件处理器应该首先检查readyState的值和HTTP状态。当请求完成加载(readyState值为4)并且响应已经完成(HTTP状态为"OK")时,你就可以调用一个JavaScript函数来处理该响应内容。下列脚本负责在响应完成时检查相应的值并调用一个 processResponse()方法。
function processRequest(){
 if(xmlHttpReq.readyState==4){
  if(xmlHttpReq.status==200){
   processResponse();
  }
 }
}


  该processResponse()方法使用XMLHttpRequest对象的responseXML和responseText 属性来检索HTTP响应。如上面所解释的,仅当在响应的媒体类型是text/xml,application/xml或以+xml结尾时,这个 responseXML才可用。这个responseText属性将以普通文本形式返回响应。对于一个XML响应,你将按如下方式检索内容:
var msg=xmlHttpReq.responseXML;


  借助于存储在msg变量中的XML,你可以使用DOM方法getElementsByTagName()来检索该元素的值:
var catalogId=msg.getElementsByTagName("catalogId")[0].firstChild.nodeValue;


  最后,通过更新Web页面的validationMessage div中的HTML内容并借助于innerHTML属性,你可以测试该元素值以创建一个要显示的消息:
if(catalogId=="valid"){
 var validationMessage = document.getElementById("validationMessage");
 validationMessage.innerHTML = "Catalog Id is Valid";
}
else
{
 var validationMessage = document.getElementById("validationMessage");
 validationMessage.innerHTML = "Catalog Id is not Valid";
}


  六、 小结

上面就是XMLHttpRequest对象使用的所有细节实现。通过不必把Web页面寄送到服务器而实现数据传送,XMLHttpRequest对象为客户端与服务器之间提供了一种动态的交互能力。你可以使用 JavaScript启动一个请求并处理相应的返回值,然后使用浏览器的DOM方法更新页面中的数据。
分享到:
评论

相关推荐

    Termux (Android 5.0+).apk.cab

    Termux (Android 5.0+).apk.cab

    基于go、vue开发的堡垒机系统(运维安全审计系统)全部资料+详细文档.zip

    【资源说明】 基于go、vue开发的堡垒机系统(运维安全审计系统)全部资料+详细文档.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

    葡萄城手册,快速上手,灵活报表

    制作报表

    基于C++与Qt的金山培训大作业源码汇总

    本项目为金山培训大作业源码汇总,采用C++与Qt技术构建,包含401个文件,涵盖106个C++源文件、72个头文件、41个PNG图片、27个项目文件以及HTML、JavaScript、CSS等多种文件类型。项目包含四个主要模块:KVector向量库、命令行会议系统、KSvg绘图板和KHttp音乐播放器。尽管最终未能入选,但展现了作者在C++编程和Qt框架应用方面的扎实功底和努力。

    (26408240)STM32F103+四个VL53L0代码(2020新).zip

    内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。

    基于课程设计:C语言爬虫、详细文档+全部资料+高分项目.zip

    【资源说明】 基于课程设计:C语言爬虫、详细文档+全部资料+高分项目.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

    (176629254)杭州电子科技大学自动控制原理期末试卷答案 复习ppt

    《自动控制原理》是自动化及相关专业的重要课程,涵盖了控制系统的基础理论和分析方法。这份资料集是针对杭州电子科技大学自动控制原理课程的期末复习资源,包含了试卷答案和复习PPT,对于学习者来说是一份非常宝贵的参考资料。 我们来看文件"9自控原理第五章习题参考答案.doc",它提供了第五章的习题解答。第五章通常涉及根轨迹法,这是分析线性系统稳定性的一种图形方法。通过绘制根轨迹,我们可以直观地理解系统动态性能的变化,例如系统的稳定性、超调量和调节时间等。 接着,"1第一章作业(答案).docx"涵盖了课程的初步概念,如控制系统的基本组成部分、控制系统的定义以及开环与闭环控制的区别。第一章的内容通常包括控制系统的基本模型,如传递函数和信号流图。 "10测试4.docx"和"13第7章测试.docx"可能是关于控制系统设计和分析的测试题目,可能涉及到频率响应分析或状态空间模型等内容。第七章常常讨论系统稳定性分析,比如奈奎斯特稳定判据或劳斯判据。 "7测试1.docx"可能包含有关拉普拉斯变换和控制系统动态特性的问题,这是控制系统分析的基础工具。 "4自动控制第二章习题答案.pdf"提供了第二章习题

    066 - 直播逗大哥话术.docx

    066 - 直播逗大哥话术

    AOP项目demo 案例

    AOP项目demo 案例

    皮带输送线3D+2DCAD+加工件标准件清单BOMsw2016可编辑全套技术资料100%好用.zip

    皮带输送线3D+2DCAD+加工件标准件清单BOMsw2016可编辑全套技术资料100%好用.zip

    154-基于stm32单片机花样流水灯设计Proteus仿真+源程序.zip

    154-基于stm32单片机花样流水灯设计Proteus仿真+源程序.zip

    鲸鱼WOA-XGboost拟合预测建模模型,数据格式多维自变量输入,单维因变量输出,直接替数据就可以使用,程序内注释详细

    鲸鱼WOA-XGboost拟合预测建模模型,数据格式多维自变量输入,单维因变量输出,直接替数据就可以使用,程序内注释详细

    基于springboot的学生综合成绩测评系统源码(java毕业设计完整源码).zip

    项目均经过测试,可正常运行! 环境说明: 开发语言:java JDK版本:jdk1.8 框架:springboot 数据库:mysql 5.7/8 数据库工具:navicat 开发软件:eclipse/idea

    RocketDock-v1.3.5

    RocketDock-v1.3.5是一款专为Windows 7设计的桌面工具,它模仿Mac OS X的Dock功能,通过直观的图标排列提高应用启动效率。该工具提供快捷启动、动画效果、自定义设置以及扩展性,使用户能够根据个人喜好调整Dock栏并减少桌面杂乱。安装和设置过程简单,配有详细的说明文件,帮助用户更好地了解和使用工具。

    基于springboot的会员制医疗预约服务管理信息系统源码(java毕业设计完整源码+LW).zip

    会员制医疗预约服务管理信息系统的作用,可以提高会员制医疗预约服务管理的工作人员的效率,协助他们对会员制医疗预约服务信息进行统一管理,为管理者提供信息储存和查询搜索系统。一个良好的会员制医疗预约服务管理信息系统可以实现对会员制医疗预约服务的精细化管理:对在线会员制医疗预约服务管理流程的全过程进行电子化操作,其主要作用是管理和控制会员制医疗预约服务所有的信息,分析库存数据,使工作人员对会员制医疗预约服务管理信息系统进行监管,根据系统所提供的相应信息,采取适当的措施,及时补救管理中的漏洞,提高在线会员制医疗预约服务管理的工作效率,使得在线会员制医疗预约服务管理变的更加系统和规范。 环境说明: 开发语言:java JDK版本:jdk1.8 框架:springboot 数据库:mysql 5.7/8 数据库工具:navicat 开发软件:eclipse/idea

    (2368806)CCNA中文版PPT

    **CCNA(思科认证网络助理工程师)是网络技术领域中的一个基础认证,它涵盖了网络基础知识、IP编址、路由与交换技术等多个方面。以下是对CCNA中文版PPT中可能涉及的知识点的详细说明:** ### 第1章 高级IP编址 #### 1.1 IPv4地址结构 - IPv4地址由32位二进制组成,通常分为四段,每段8位,用点分十进制表示。 - 子网掩码用于定义网络部分和主机部分,如255.255.255.0。 - IP地址的分类:A类、B类、C类、D类(多播)和E类(保留)。 #### 1.2 子网划分 - 子网划分用于优化IP地址的分配,通过借用主机位创建更多的子网。 - 子网计算涉及掩码位数选择,以及如何确定可用的主机数和子网数。 - CIDR(无类别域间路由)表示法用于更有效地管理IP地址空间。 #### 1.3 私有IP地址 - 为了节省公网IP地址,私有IP地址被用于内部网络,如10.0.0.0/8,172.16.0.0/12,192.168.0.0/16。 #### 1.4 广播地址 - 每个网络都有一个特定的广播地址,所有数据包都会发送到这个地址以达到同一网络内的所有设备。

    基于springboot的藏区特产销售平台源码(java毕业设计完整源码+LW).zip

    项目实现了对特产信息管理、特产分类管理、特产分类管理、特产评分管理、系统管理、订单管理等业务进行管理。 环境说明: 开发语言:java JDK版本:jdk1.8 框架:springboot 数据库:mysql 5.7/8 数据库工具:navicat 开发软件:eclipse/idea

    “个人事务云服务”:日常事务管理系统的云计算应用

    随着21世纪网络和计算机技术的飞速发展,它们已经与我们的日常生活紧密融合。当前网络的运行速度已经达到千兆级别,覆盖范围广泛,深入到生活的每一个角落。这一进步推动了管理系统的发展,使得远程处理事务、远程工作信息管理和实时追踪工作状态成为可能。网上管理系统以其前所未有的体验满足了新时代的工作需求,因此得到了大力发展。 本系统是一个个人日常事务管理系统,利用计算机和网络技术开发的在线平台,能够实现日常安排、消费记录和重要提醒设置等功能。系统采用SSM框架和Vue技术,数据库使用MySQL,开发环境为Eclipse。系统用户角色包括普通用户和管理员,功能涵盖个人中心管理、用户管理、日常安排管理、消费记录管理和重要提醒管理。用户可以记录消费、安排日常事务和设置重要提醒,而管理员则负责管理用户信息和基础数据信息。该系统不仅方便了用户和管理员,还提高了个人事务管理的效率,更适应现代人的生活方式。

    基于java+springboot+mysql+微信小程序的考研资料分享系统 源码+数据库+论文(高分毕业设计).zip

    项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea、微信开发者工具 数据库:MySql5.7以上 部署环境:maven 数据库工具:navicat

    如何使用Python和PIL库生成带竖排文字的封面图像

    如何使用Python和PIL库生成带竖排文字的封面图像

Global site tag (gtag.js) - Google Analytics