fcgiCodec

2025-12-07 0 529

fcgiCodec

fcgiCodec是用于编码和解码FASTCGI协议消息的Miniscript编程语言的库。

这不是一个可以管理网络的完整快速库,只涵盖了数据的编码和解码。

您的平台应具有RAWDATA类的实现。

安装

您只需要此文件: lib/ fcgiCodec .ms

测试

运行./mm-test.sh以在迷你微型中进行测试。

运行./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,但有几个缺点:

  • 记录只知道他们的recordTyperequestIdbody ,特定于类型的信息不会被解析(您可以轻松地用胡说八道制作记录)。
  • 必须手动完成“流”处理 – 破坏并粘合连续的流记录类型的有效载荷。

为了克服这些问题,请使用 *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 (如果存在EndRequestMsgfcgiCodec .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 (如果存在DataMsgFCGI_FILTER角色作为RawData的数据
dataString (如果存在DataMsgFCGI_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_VALUESFCGI_GET_VALUES_RESULTFCGI_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)。

这些家伙的头盔“快速”。

下载源码

通过命令行克隆项目:

git clone https://github.com/marcgurevitx/fcgiCodec.git

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 开发教程 fcgiCodec https://www.zuozi.net/31584.html

fastresize
上一篇: fastresize
LaraSible
下一篇: LaraSible
常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务