fcgiCodec
fcgiCodec是用于编码和解码FASTCGI协议消息的Miniscript编程语言的库。
这不是一个可以管理网络的完整快速库,只涵盖了数据的编码和解码。
您的平台应具有RAWDATA类的实现。
安装
您只需要此文件: lib/ fcgiCodec .ms 。
测试
运行./cl-test.sh以命令行测试。
fastcgi服务器的示例
此示例是坐在Nginx后面的微型录制程序,提供HTTP页面。每个新页面都会显示每个请求上会增加的数字。
笔记:
- 对此示例的Miniscript解释器进行修补,以通过UDS模块支持UNIX域插座。
- nginx配置在这里非常小,除了
listen,它只是一个fastcgi_pass指令:fastcgi_pass unix:/tmp/ fcgiCodec -example.sock;。 - 当此程序启动时,它会重新创建套接字文件,并且显然Nginx由于新文件的使用权在我的计算机上的工作方式如何。因此,我个人所做的是用人的手将许可标志更改为
a+w。嘿,我是编码员,而不是DevOps。 - 为了简洁起见,省略了错误检查,并且未处理各种特殊情况。
请参阅代码…
fcgiCodec\”
// Initialize network.
srv = uds.createServer(\”/tmp/ fcgiCodec -example.sock\”)
print \”Listening at /tmp/ fcgiCodec -example.sock …\”
// Create a RecordDecoder to convert raw socket data into `Record` objects.
decoder = fcgiCodec .RecordDecoder.make
// Create a Bucket to collect the data for each individual request (it will also convert `Record` objects into appropriate `*Msg` objects).
bucket = fcgiCodec .Bucket.make
// Our state:
n = 0 // we\’ll increment it on each request
conn = null
while true
yield
print \”Waiting for connection… \”, \”\”
if conn == null then conn = srv.accept(-1) // wait for Nginx to make a connection to us
print \”OK\”
print \”Waiting for data… \”, \”\”
data = conn.receive(-1, -1) // wait for Nginx to send us request data
if data == null then continue
print \”OK. Got \” + data.len + \” bytes.\”
// Convert data into records via the decoder, and put those records into the bucket.
decoder.pushData data
records = decoder.getAllRecords
print \”Decoded \” + records.len + \” records.\”
bucket.pushManyRecords records
// Writing a callback for `Bucket.handleAll()`.
closeP = false
handler = function(request, arg)
print \”Handling request #\” + request.requestId + \”…\”
// Here we might expect to meet some special cases:
// – Management message
// – Unfinished request
// – Aborted request
// – Unknown role
// …
// For the sake of simplicity we\’ll only cover regular requests of a \”responder\” role.
// Did we read the params yet?
if request.params == null then return false // false: we\’re not done with the request, keep it in the bucket
// Touch state
outer.n += 1
// Compose a response
rsp = \”Content-Type: text/html\” + char(13) + char(10) +
\”\” + char(13) + char(10) +
\”<h1>hello fcgiCodec ({n})</h1> params: {params}\”
rsp = rsp.replace(\”{n}\”, outer.n)
rsp = rsp.replace(\”{params}\”, str(request.params))
// Return response to Nginx
msg = fcgiCodec .StdoutMsg.make(request.requestId, rsp)
for record in msg.toRecords
conn.send record.toRawData // return \”CGI stdout\”
end for
msg = fcgiCodec .EndRequestMsg.make(request.requestId, 0, fcgiCodec .protoStatus.FCGI_REQUEST_COMPLETE)
for record in msg.toRecords
conn.send record.toRawData // tell Nginx that we\’re done
end for
if not request.keepConnectionP then outer.closeP = true // did Nginx ask us to close the connection?
return true // true: we\’re done with this request, delete it from the bucket
end function
// Handle requests (if any).
bucket.handleAll null, @handler
if closeP then
conn.close
conn = null
closeP = false
end if
end while\”>
import \" fcgiCodec \" // Initialize network. srv = uds . createServer ( \"/tmp/ fcgiCodec -example.sock\" ) print \"Listening at /tmp/ fcgiCodec -example.sock ...\" // Create a RecordDecoder to convert raw socket data into `Record` objects. decoder = fcgiCodec . RecordDecoder . make // Create a Bucket to collect the data for each individual request (it will also convert `Record` objects into appropriate `*Msg` objects). bucket = fcgiCodec . Bucket . make // Our state: n = 0 // we\'ll increment it on each request conn = null while true yield print \"Waiting for connection... \" , \"\" if conn == null then conn = srv . accept ( -1 ) // wait for Nginx to make a connection to us print \"OK\" print \"Waiting for data... \" , \"\" data = conn . receive ( -1 , -1 ) // wait for Nginx to send us request data if data == null then continue print \"OK. Got \" + data . len + \" bytes.\" // Convert data into records via the decoder, and put those records into the bucket. decoder . pushData data records = decoder . getAllRecords print \"Decoded \" + records . len + \" records.\" bucket . pushManyRecords records // Writing a callback for `Bucket.handleAll()`. closeP = false handler = function ( request , arg ) print \"Handling request #\" + request . requestId + \"...\" // Here we might expect to meet some special cases: // - Management message // - Unfinished request // - Aborted request // - Unknown role // ... // For the sake of simplicity we\'ll only cover regular requests of a \"responder\" role. // Did we read the params yet? if request . params == null then return false // false: we\'re not done with the request, keep it in the bucket // Touch state outer . n += 1 // Compose a response rsp = \"Content-Type: text/html\" + char ( 13 ) + char ( 10 ) + \"\" + char ( 13 ) + char ( 10 ) + \"<h1>hello fcgiCodec ({n})</h1> params: {params}\" rsp = rsp . replace ( \"{n}\" , outer . n ) rsp = rsp . replace ( \"{params}\" , str ( request . params )) // Return response to Nginx msg = fcgiCodec . StdoutMsg . make ( request . requestId , rsp ) for record in msg . toRecords conn . send record . toRawData // return \"CGI stdout\" end for msg = fcgiCodec . EndRequestMsg . make ( request . requestId , 0 , fcgiCodec . protoStatus . FCGI_REQUEST_COMPLETE ) for record in msg . toRecords conn . send record . toRawData // tell Nginx that we\'re done end for if not request . keepConnectionP then outer . closeP = true // did Nginx ask us to close the connection? return true // true: we\'re done with this request, delete it from the bucket end function // Handle requests (if any). bucket . handleAll null , @ handler if closeP then conn . close conn = null closeP = false end if end while
概述
FastCGI客户端和服务器交换数据包装在名为“记录”的二进制结构中。
该库提供了两个类以在代码中管理此类记录的类:记录类和RecordDecoder类。
要创建一个记录对象,请调用Record.make()工厂。然后,要获取其原始字节,请调用toRawData()方法。
如果您在原始数据中编码了一系列记录,并且想从中获取单个记录,请使用RecordDecoder.make()工厂创建一个解码器,请使用其pushData()方法将数据馈送到其中,然后使用getRecord()或getAllRecords()方法收集结果Record对象。
这两个课程提供了一个简单的API,但有几个缺点:
- 记录只知道他们的
recordType,requestId和body,特定于类型的信息不会被解析(您可以轻松地用胡说八道制作记录)。 - 必须手动完成“流”处理 – 破坏并粘合连续的流记录类型的有效载荷。
为了克服这些问题,请使用 *msg类(“消息”)。它们就像Record类一样,但具有可以读取或修改的每种记录类型的特定属性。此外,流记录将其身体粘合到一个消息对象下的单个数据块中。要创建消息,请make()特定消息类的工厂。请参阅下面的每个*Msg类具有哪些属性。
没有特殊的方法来生成消息的RawData ,而是首先使用toRecords()方法,然后调用每个记录的toRawData() 。也没有方法可以从RawData流中解析消息(我不需要它,所以我没有编码) – 但是,如果您有记录并且要将它们转换为消息,则可以使用Bucket对象来执行此操作。
当客户端可以同时发送几个请求的FastCGI记录时,Bucket类可帮助您进行多路复用/交织的工作流程。您可以使用其pushRecord()方法将记录添加到Bucket中,然后使用handleOne()或handleAll()方法处理请求。
Bucket累积的请求对象不是某些类的实例,而只是以消息作为值以及某些属性的Adhoc映射。 (请注意,如果您的代码在客户端端运行并且正在从服务器中消费数据,那么您获得的不是技术上的请求,而是响应,而是Bucket不在乎。)
NameValuepairs类非常方便,具有记录类型,这些类型以“名称值对”格式编码其身体(<Name_length> <name_length> <value_length> <NAMANGENTS> <NAME> <name> <value> <value> …等)。在大多数情况下, NameValuePairs对象无需直接创建,而是通过*Msg或请求对象暴露。但是,您也可以通过调用NameValuePairs.make()工厂来创建一个。另外,没有人会阻止您自己在fastcgi上下文上使用NameValuePairs ,只是为了序列化平面名称值地图。
管理名称值对,使用hasName() , getValue() , setValue()和deleteValue()方法。如果您想从RawData块中解析名称值对,请使用pushData()方法,并获取RAW字节,请致电toRawData()方法。
关于NameValuePairs类的另一个注释:无论您输入的数据是什么数据(通过setValue()或pushData() ),键和值都将始终转换为字符串。
另外,如果某些类的方法接受RawData作为参数,则它也会愉快地接受字符串并在场景后面转换为RawData 。
最后,如果您读取代码,您会找到一个RawDataCollection类,该类用于粘合和切片RawData块。非常方便,您可能不需要直接操纵它。
记录课
Record类代表FASTCGI记录。
| 属性 /方法 | 描述 |
|---|---|
recordType |
从fcgiCodec .recordType枚举的记录类型 |
requestId |
请求ID(管理类型0 ) |
make() (类方法) |
返回一个新的Record对象 |
body() |
返回记录正文的RawData
|
toRawData()
|
返回整个记录的RawData (头部 +身体) |
fcgiCodec .recordType中的常数枚举(与规范中的数字相同):
recordType . FCGI_BEGIN_REQUEST = 1 recordType . FCGI_ABORT_REQUEST = 2 recordType . FCGI_END_REQUEST = 3 recordType . FCGI_PARAMS = 4 recordType . FCGI_STDIN = 5 recordType . FCGI_STDOUT = 6 recordType . FCGI_STDERR = 7 recordType . FCGI_DATA = 8 recordType . FCGI_GET_VALUES = 9 recordType . FCGI_GET_VALUES_RESULT = 10 recordType . FCGI_UNKNOWN_TYPE = 11
record.make()
Record.make(recordType, requestId = 0, body) -> Record
(类方法)返回一个新的Record对象。
body参数可以是RawData对象或字符串。
此方法不会解析或检查body 。为了确保类型特定属性的正确性使用*Msg类。
record.body()
record.body() -> RawData
返回记录正文的RawData 。
record.torawdata()
record.toRawData() -> RawData
返回整个记录的RawData (头部 +正文)。
RecordDecoder类
RecordDecoder类可用于读取原始数据流并从中提取Record对象。
| 属性 /方法 | 描述 |
|---|---|
make() (类方法) |
返回一个新的RecordDecoder对象 |
pushData() |
将原始数据块附加到内部数据流 |
getRecord() |
从流中获取一个记录(如果有) |
getAllRecords()
|
从流中获取所有可用的记录 |
recorddecoder.make()
RecordDecoder.make() -> RecordDecoder
(类方法)返回一个新的RecordDecoder对象。
recorddecoder.pushdata()
recordDecoder.pushData(r, onError = null) -> null | onError result
将原始数据块附加到内部数据流。
解析立即开始,并解码尽可能多的记录。
如果没有足够的数据来进行整个记录,则解析会停止,直到下一个呼叫pushData()带来更多输入。
如果解析遇到错误,则默认操作是qa.abort() (意味着,它崩溃了程序)。
如果提供了onError()回调,则将其称为qa.abort() (表示防止崩溃)。
onError()回调至少应具有以下参数: onError(errCode, arg1) 。
报告了以下错误条件:
错误代码( errCode ) |
描述 |
|---|---|
\"UNKNOWN_PROTO\" |
协议字节(0)具有未知值, arg1保持值 |
\"UNKNOWN_RECORD_TYPE\" |
记录类型字节(1)具有未知值, arg1保持值 |
onError()回调的返回值成为pushData()的返回值。
错误之后, RecordDecoder对象变得无法使用,因为它无法通过错误的字节移动。
如果未遇到错误或onError()返回null ,则pushData()的结果为null 。
recorddecoder.getRecord()
recordDecoder.getRecord() -> Record | null
从流中获取一个记录(如果有)。
recorddecoder.getAllRecords()
recordDecoder.getAllRecords() -> list of Record objects
从流中获取所有可用的记录。
*msg类
消息类处理记录类型相关的属性,将流记录连接到单个对象中,并解码某些记录类型的名称值对。
每个记录类型都有一个相应的消息类(它们都有可预测的名称)。
| 记录类型 | 班级 |
|---|---|
recordType.FCGI_BEGIN_REQUEST |
BeginRequestMsg
|
recordType.FCGI_ABORT_REQUEST
|
AbortRequestMsg
|
recordType.FCGI_END_REQUEST
|
EndRequestMsg
|
recordType.FCGI_PARAMS
|
ParamsMsg
|
recordType.FCGI_STDIN
|
StdinMsg
|
recordType.FCGI_STDOUT
|
StdoutMsg
|
recordType.FCGI_STDERR
|
StderrMsg
|
recordType.FCGI_DATA
|
DataMsg
|
recordType.FCGI_GET_VALUES
|
GetValuesMsg
|
recordType.FCGI_GET_VALUES_RESULT
|
GetValuesResultMsg
|
recordType.FCGI_UNKNOWN_TYPE
|
UnknownTypeMsg
|
所有*Msg类的常见API:
| 属性 /方法 | 描述 |
|---|---|
recordType |
从fcgiCodec .recordType枚举的记录类型 |
requestId |
请求ID(管理类型0 ) |
make() (类方法) |
返回一个新的消息对象(此方法的签名对于每个类都不同) |
toRecords()
|
返回形成消息的记录列表 |
其他属性由要make()的参数定义。
beginrequestmsg.make()
BeginRequestMsg.make(requestId, role, keepConnectionP) -> BeginRequestMsg
(类方法)返回一个新的BeginRequestMsg对象并设置其属性。
| 参数 /属性 | 描述 |
|---|---|
requestId |
请求ID |
role
|
fcgiCodec .role的FASTCGI角色。 |
keepConnectionP
|
如果为true,服务器应在服务请求后关闭网络连接 |
fcgiCodec .role枚举中的常数(与规范中的数字相同):
role . FCGI_RESPONDER = 1 role . FCGI_AUTHORIZER = 2 role . FCGI_FILTER = 3
abortrequestmsg.make()
AbortRequestMsg.make(requestId) -> AbortRequestMsg
(类方法)返回一个新的AbortRequestMsg对象并设置其属性。
| 参数 /属性 | 描述 |
|---|---|
requestId |
请求ID |
endrequestmsg.make()
EndRequestMsg.make(requestId, appStatus, protoStatus) -> EndRequestMsg
(类方法)返回一个新的EndRequestMsg对象并设置其属性。
| 参数 /属性 | 描述 |
|---|---|
requestId |
请求ID |
appStatus
|
CGI计划退出状态 |
protoStatus |
fcgiCodec .protoStatus的协议状态。 |
fcgiCodec .protoStatus Enum中的常数(与规范相同的数字):
protoStatus . FCGI_REQUEST_COMPLETE = 0 protoStatus . FCGI_CANT_MPX_CONN = 1 protoStatus . FCGI_OVERLOADED = 2 protoStatus . FCGI_UNKNOWN_ROLE = 3
paramsmsg.make()
ParamsMsg.make(requestId, paramsMap) -> ParamsMsg
(类方法)返回一个新的ParamsMsg对象并设置其属性。
| 参数 /属性 | 描述 |
|---|---|
requestId |
请求ID |
paramsMap
|
CGI参数地图 |
stdinmsg | stdoutmsg | stderrmsg | datamsg .make()
StdinMsg.make(requestId, data) -> StdinMsg
StdoutMsg.make(requestId, data) -> StdoutMsg
StderrMsg.make(requestId, data) -> StderrMsg
DataMsg.make(requestId, data) -> DataMsg
(类方法)返回一个新的*Msg对象并设置其属性。
| 参数 /属性 | 描述 |
|---|---|
requestId |
请求ID |
data
|
原始数据主体 |
getValuesmsg.make()
GetValuesMsg.make(names) -> GetValuesMsg
(类方法)返回一个新的GetValuesMsg对象并设置其属性( requestId = 0 )。
| 参数 /属性 | 描述 |
|---|---|
names |
请求的变量名称列表 |
getValuesResultmsg.make()
GetValuesResultMsg.make(valuesMap) -> GetValuesResultMsg
(类方法)返回一个新的GetValuesResultMsg对象并设置其属性( requestId = 0 )。
| 参数 /属性 | 描述 |
|---|---|
valuesMap |
请求变量的地图 |
unknowntypemsg.make()
UnknownTypeMsg.make(unknownType) -> UnknownTypeMsg
(类方法)返回一个新的UnknownTypeMsg对象并设置其属性( requestId = 0 )。
| 参数 /属性 | 描述 |
|---|---|
unknownType |
未知类型 |
msg.torecords()
msg.toRecords(chunkLength = null) -> list of Record objects
返回形成消息的记录列表。
该列表将包含一个非流记录类型的记录,以及两个或多个流记录类型的记录(此类记录的最后一个记录将始终具有空体)。
可选的chunkLength参数仅对流类型有意义,而对于其他类型则忽略了。
如果给出,它表示每个单独的记录主体的最大长度。如果null ,则将整个身体编码为单个记录,然后是空体记录。
水桶类
Bucket可用于收集Record对象并构建“请求”对象 – 邮件集合。
Bucket的工作流与解码器不同,因为您不从存储桶中获取请求,而是将其处理到位。
这样做的原因是,在收到所有消息之前,允许服务器开始响应请求。
| 属性 /方法 | 描述 |
|---|---|
make() (类方法) |
返回一个新的水桶对象 |
nRequests() |
返回存储桶中的请求数 |
requestIds() |
返回请求ID列表 |
pushRecord() |
将Record对象附加到内部状态 |
pushManyRecords() |
将Record对象的列表附加到内部状态 |
handleOne() |
处理请求对象 |
handleAll() |
处理所有请求对象 |
removeRequest() |
从存储桶中删除请求对象并将其返回 |
bucket.make()
Bucket.make() -> Bucket
(类方法)返回一个新的Bucket对象。
bucket.nrequests()
bucket.nRequests() -> number
返回存储桶中的请求数。
bucket.requestids()
bucket.requestIds() -> list of request IDs
返回请求ID列表。
bucket.pushrecord()
bucket.pushRecord(record, onError = null) -> null | onError result
将Record对象附加到内部状态。
检查记录是否为其类型,并在存储桶中创建并保存一个相应的消息对象。
未完成的消息将单独保存,直到下一个致电pushRecord()带来更多记录。
如果转换为消息会遇到错误,则默认操作是qa.abort() (这意味着,它崩溃了程序)。
如果提供了onError()回调,则将其称为qa.abort() (表示防止崩溃)。
onError()回调至少应具有这些参数: onError(errCode, arg1, arg2) 。
报告了以下错误条件:
错误代码( errCode ) |
描述 |
|---|---|
\"DIRTY_BUCKET\" |
具有相同记录类型( arg1 )和请求ID( arg2 )的消息已经存在 |
\"BODY_TOO_SHORT\" |
记录正文中的字节不足以解析其属性,需要arg1字节,获得arg2字节 |
\"UNKNOWN_ROLE\" |
FCGI_BEGIN_REQUEST记录主体的角色字节(0-1)具有未知值, arg1保持值 |
\"UNKNOWN_PROTO_STATUS\" |
FCGI_END_REQUEST记录主体的协议状态字节(4)具有未知值, arg1保持值 |
\"TOO_MANY_MSGS\" |
水桶收集了太多未完成的流消息 |
\"TOO_MANY_RECORDS\" |
该存储桶收集了一个未完成的arg1流消息中的记录,请求ID arg2
|
onError()处理错误后,错误的记录将被拒绝,并且存储桶可以继续消耗更多记录。但是,其中一些条件可能需要采取行动:
- 如果
\"DIRTY_BUCKET\"发生了,因为您忘记删除请求,请使用removeRequest()删除它。 - 可以通过增加
bucket.maxMsgs常数来绕过\"TOO_MANY_MSGS\"。 - 可以通过增加
bucket.maxNRecordsPerMsg常数来绕过\"TOO_MANY_RECORDS\"。
onError()回调的返回值成为pushRecord()的返回值。
如果没有遇到错误或onError()返回null ,则pushRecord()的结果为null 。
bucket.pushmanyrecords()
bucket.pushManyRecords(records, onError = null) -> list of nulls or onError results
将Record对象的列表附加到内部状态。
基本上,每个记录都在循环中调用pushRecord() 。
bucket.handleone()
bucket.handleOne(requestId, arg, cb) -> null
使用请求ID请求ID请求对象处理请求requestId 。
如果不存在此类ID的请求,则不存在。
如果请求存在,则将调用cb()带有以下参数: cb(request, arg) ,其中request是包含请求ID的所有收集消息的地图。
除了recordType => message对外,还将更多属性设置为请求映射,该属性仅代理基础消息的属性。
请求地图的属性:
| 属性 /方法 | 描述 |
|---|---|
requestId |
请求ID |
| <记录类型> | <消息对象> |
| … | … |
role
|
(如果存在BeginRequestMsg ) |
keepConnectionP
|
(如果存在BeginRequestMsg ) |
isAborted
|
如果存在AbortRequestMsg ,则为 |
appStatus |
(如果存在EndRequestMsg )CGI退出代码 |
protoStatus |
(如果存在EndRequestMsg ) fcgiCodec .protoStatus的常数 |
params |
(如果存在ParamsMsg )CGI参数的地图 |
stdin |
(如果存在StdinMsg )CGI STDIN作为RawData
|
stdinString
|
(如果存在StdinMsg )CGI stdin作为字符串 |
stdout |
(如果存在StdoutMsg )CGI STDOUT作为RawData
|
stdoutString
|
(如果存在StdoutMsg )CGI STDOUT作为字符串 |
stderr |
(如果存在StderrMsg )CGI STDERR作为RawData
|
stderrString
|
(如果存在StderrMsg )CGI stderr作为字符串 |
data |
(如果存在DataMsg ) FCGI_FILTER角色作为RawData的数据 |
dataString |
(如果存在DataMsg ) FCGI_FILTER角色作为字符串的数据 |
names |
(如果存在GetValuesMsg )请求变量的名称列表 |
result |
(如果存在GetValuesResultMsg )请求变量的地图 |
unknownType |
(如果存在UnknownTypeMsg )未知的记录类型 |
如果不存在一些消息,则其附加属性的值为null 。
如果是在请求下完成的,则回调应返回true 。该请求将从存储桶中删除。
否则,该请求将保存在一个存储桶中,而handleOne()方法将尝试在下一个调用中再次处理它。 (如果您期望将以后的同一请求ID到达更多消息,则可能需要这样做)。
bucket.handleall()
bucket.handleAll(arg, cb) -> null
处理所有请求对象。
基本上,每个请求中的循环中调用handleOne() 。
bucket.removerequest()
bucket.removeRequest(requestId) -> request map
从存储桶中删除请求对象并将其返回。
nameValuepairs
NameValuePairs类解码来自FCGI_GET_VALUES , FCGI_GET_VALUES_RESULT和FCGI_PARAMS记录类型的名称值对。
请参阅格式的规范。
名称和值存储为迷你字符串。
| 属性 /方法 | 描述 |
|---|---|
map |
名称配对的地图 |
make() (类方法) |
返回一个新NameValuePairs对象 |
pushData() |
将原始数据块附加到内部数据收集 |
names() |
返回所有名称 |
getValue() |
按名称返回值 |
setValue() |
按名称设置值 |
deleteValue() |
按名称删除值 |
hasName() |
如果存在名称值对 |
toRawData() |
返回编码的原始数据 |
nameValuepairs.make()
NameValuePairs.make(rr = null) -> NameValuePairs
(类方法)返回一个新的NameValuePairs对象。
如果给出了可选的数据块列表,则将为名称值解析。
nameValuepairs.pushdata()
nameValuePairs.pushData(r) -> null
将原始数据块附加到内部数据收集中。
解析立即开始,并解码尽可能多的名称配对。
如果没有足够的数据来完成一对,则解析将停止,直到下一个呼叫pushData()带来更多输入。
nameValuepairs.names()
nameValuePairs.names() -> list of names
返回所有名称。
nameValuepairs.getValue()
nameValuePairs.getValue(name, default = null) -> value | default
按名称返回值。
如果对不存在,则返回default 。
nameValuepairs.setValue()
nameValuePairs.setValue(name, value) -> null
按名称设置值。
nameValuepairs.deletevalue()
nameValuePairs.deleteValue(name) -> null
按名称删除值。
nameValuepairs.hasname()
nameValuePairs.hasName(name) -> true | false
如果存在名称值对,则返回true。
nameValuepairs.torawdata()
nameValuePairs.toRawData() -> RawData
返回编码的原始数据。
社会艺术
乔·斯特劳特(Joe Strout)的麦片《丁香》(Minnie)。
这些家伙的头盔“快速”。
