乐闻世界logo
搜索文章和话题

面试题手册

cURL 如何处理 HTTP 重定向?

重定向处理是 cURL 访问 Web 资源时的常见需求。正确处理重定向能确保请求到达最终目标地址。基本重定向处理# 不跟随重定向(默认行为)curl http://example.com# 自动跟随重定向curl -L http://example.comcurl --location http://example.com# 显示重定向过程curl -L -v http://example.com重定向次数限制# 限制最大重定向次数(默认 50 次)curl -L --max-redirs 5 http://example.com# 无限重定向(不推荐)curl -L --max-redirs -1 http://example.comPOST 请求重定向行为# 默认:POST 重定向后转为 GETcurl -L -X POST -d "data=test" http://example.com/submit# 保持 POST 方法(RFC 7231)curl -L --post301 -X POST -d "data=test" http://example.com/submit# 保持 POST 方法(非标准)curl -L --post302 -X POST -d "data=test" http://example.com/submitcurl -L --post303 -X POST -d "data=test" http://example.com/submit重定向类型说明| 状态码 | 类型 | 默认行为 || --- | --------- | ---------- || 301 | 永久重定向 | POST → GET || 302 | 临时重定向 | POST → GET || 303 | See Other | POST → GET || 307 | 临时重定向 | 保持方法 || 308 | 永久重定向 | 保持方法 |查看重定向链# 显示所有重定向 URLcurl -L -v http://example.com 2>&1 | grep -E "(< HTTP|< Location)"# 使用 -w 输出最终 URLcurl -L -w "Final URL: %{url_effective}\n" -o /dev/null -s http://example.com# 获取重定向次数curl -L -w "Redirects: %{num_redirects}\n" -o /dev/null -s http://example.com常见重定向场景# HTTP 到 HTTPS 重定向curl -L http://example.com# www 到非 www 重定向curl -L http://www.example.com# 短链接展开curl -L -w "Final URL: %{url_effective}\n" -o /dev/null -s https://bit.ly/shorturl# 登录后重定向curl -L -c cookies.txt -b cookies.txt \ -X POST \ -d "username=admin&password=123456" \ http://example.com/login高级重定向控制# 限制重定向协议curl -L --proto-redir =https http://example.com# 重定向时保持请求头curl -L --location-trusted \ -H "Authorization: Bearer token123" \ http://example.com/api# 查看重定向前后的 Cookiecurl -L -c cookies.txt -b cookies.txt -v http://example.com 2>&1 | grep -i cookie# 重定向时修改请求头curl -L -H "Host: newdomain.com" http://example.com重定向调试脚本#!/bin/bash# 重定向追踪脚本URL="http://example.com"echo "========== 重定向链追踪 =========="echo "初始 URL: $URL"echo ""# 方法一:使用 -v 查看curl -L -v "$URL" 2>&1 | grep -E "(< HTTP|< Location)" | head -20echo ""echo "========== 最终信息 =========="# 获取最终 URLFINAL_URL=$(curl -L -w "%{url_effective}" -o /dev/null -s "$URL")echo "最终 URL: $FINAL_URL"# 获取重定向次数REDIRECTS=$(curl -L -w "%{num_redirects}" -o /dev/null -s "$URL")echo "重定向次数: $REDIRECTS"# 获取最终状态码STATUS=$(curl -L -w "%{http_code}" -o /dev/null -s "$URL")echo "最终状态码: $STATUS"常见问题解决# 问题 1:重定向循环# 解决:限制重定向次数curl -L --max-redirs 10 http://example.com# 问题 2:跨域重定向丢失认证# 解决:使用 --location-trustedcurl -L --location-trusted -H "Authorization: Bearer token" http://example.com# 问题 3:POST 数据丢失# 解决:使用 --post302 或 --post301curl -L --post302 -X POST -d "data=test" http://example.com# 问题 4:HTTPS 重定向失败# 解决:确保 SSL 证书有效或使用 -kcurl -L -k http://example.com# 问题 5:重定向到不同域名# 解决:检查并更新 Host 头curl -L -H "Host: newdomain.com" http://example.com实战示例# 完整的重定向感知 API 调用curl -L --max-redirs 5 \ --post302 \ -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -d '{"action":"submit"}' \ -w "\n最终URL: %{url_effective}\n重定向次数: %{num_redirects}\nHTTP状态: %{http_code}\n" \ http://api.example.com/submit# 短链接批量展开for url in "bit.ly/abc" "t.co/xyz"; do echo -n "$url -> " curl -L -w "%{url_effective}" -o /dev/null -s "https://$url" echo ""done​
阅读 0·3月6日 23:08

Solidity 中如何使用 Assembly 进行底层优化?有哪些注意事项?

Assembly(汇编)是 Solidity 中的底层编程方式,允许开发者直接操作 EVM 字节码。虽然它提供了极高的灵活性和 Gas 优化空间,但也增加了代码复杂性和安全风险。1. 为什么使用 Assemblycontract AssemblyBenefits { // 1. Gas 优化:跳过 Solidity 的高级抽象 // 2. 访问底层 EVM 特性 // 3. 实现 Solidity 不支持的操作 // 4. 精细控制内存和存储}2. Assembly 基础语法内联汇编(Inline Assembly)contract BasicAssembly { // 使用 assembly 关键字 function add(uint256 a, uint256 b) external pure returns (uint256 result) { assembly { // 直接使用 EVM 操作码 result := add(a, b) // 加法操作 } } // 多个操作 function calculate(uint256 x) external pure returns (uint256) { assembly { let y := add(x, 10) // 局部变量 let z := mul(y, 2) // 乘法 mstore(0x00, z) // 存储到内存 return(0x00, 32) // 返回内存中的值 } }}数据类型和操作contract AssemblyDataTypes { // 基本类型操作 function dataTypes() external pure { assembly { // 整数 let a := 100 let b := 0xFF // 十六进制 // 布尔(0 或 1) let flag := 1 // 地址 let addr := 0x1234567890123456789012345678901234567890 // 算术运算 let sum := add(a, b) let diff := sub(a, b) let prod := mul(a, b) let quot := div(a, b) let rem := mod(a, b) // 位运算 let andResult := and(a, b) let orResult := or(a, b) let xorResult := xor(a, b) let notResult := not(a) let shifted := shl(2, a) // 左移 let shiftedR := shr(2, a) // 右移 } }}3. 内存操作内存布局contract MemoryOperations { /* 内存布局: 0x00 - 0x3f (64 bytes): 哈希函数的临时空间 0x40 - 0x5f (32 bytes): 当前分配的内存大小(空闲内存指针) 0x60 - ...: 实际数据存储 */ function memoryBasics() external pure returns (uint256) { assembly { // 读取空闲内存指针 let freePtr := mload(0x40) // 在空闲内存位置存储数据 mstore(freePtr, 12345) // 更新空闲内存指针(32 字节 = 0x20) mstore(0x40, add(freePtr, 0x20)) // 读取内存 let value := mload(freePtr) mstore(0x00, value) return(0x00, 32) } } // 存储多个值 function storeMultiple() external pure returns (uint256, uint256) { assembly { let freePtr := mload(0x40) // 存储多个 32 字节值 mstore(freePtr, 100) mstore(add(freePtr, 0x20), 200) mstore(add(freePtr, 0x40), 300) // 更新空闲内存指针 mstore(0x40, add(freePtr, 0x60)) // 返回前两个值 mstore(0x00, mload(freePtr)) mstore(0x20, mload(add(freePtr, 0x20))) return(0x00, 64) } } // 字节操作 function byteOperations() external pure returns (bytes32) { assembly { let value := 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef // 提取单个字节(从右往左,0-31) let byte0 := byte(31, value) // 最后一个字节 let byte1 := byte(30, value) // 倒数第二个字节 mstore(0x00, byte0) return(0x00, 32) } }}4. 存储操作存储布局contract StorageOperations { uint256 public value1; // slot 0 uint256 public value2; // slot 1 mapping(address => uint256) public balances; // slot 2 address public owner; // slot 3 function storageRead() external view returns (uint256) { assembly { // 读取 slot 0 的值 let v := sload(0) mstore(0x00, v) return(0x00, 32) } } function storageWrite(uint256 _value) external { assembly { // 写入 slot 0 sstore(0, _value) } } // 读取 mapping function readMapping(address _key) external view returns (uint256) { assembly { // mapping 的存储位置计算:keccak256(key . slot) mstore(0x00, _key) mstore(0x20, 2) // balances 在 slot 2 let slot := keccak256(0x00, 0x40) let value := sload(slot) mstore(0x00, value) return(0x00, 32) } } // 写入 mapping function writeMapping(address _key, uint256 _value) external { assembly { mstore(0x00, _key) mstore(0x20, 2) let slot := keccak256(0x00, 0x40) sstore(slot, _value) } } // 读取动态数组 uint256[] public dynamicArray; // slot 4 function readArrayLength() external view returns (uint256) { assembly { // 动态数组的长度存储在声明的 slot let length := sload(4) mstore(0x00, length) return(0x00, 32) } } function readArrayElement(uint256 _index) external view returns (uint256) { assembly { // 数组元素的存储位置:keccak256(slot) + index mstore(0x00, 4) let baseSlot := keccak256(0x00, 0x20) let elementSlot := add(baseSlot, _index) let value := sload(elementSlot) mstore(0x00, value) return(0x00, 32) } }}5. 函数调用和返回值外部调用contract ExternalCalls { function callExternal(address _target, bytes memory _data) external returns (bool success, bytes memory result) { assembly { // 获取数据位置和大小 let dataPtr := add(_data, 0x20) // 跳过长度字段 let dataSize := mload(_data) // 分配内存用于返回数据 let resultPtr := mload(0x40) // 执行调用 success := call( gas(), // 剩余 Gas _target, // 目标地址 0, // 发送的 ETH dataPtr, // 输入数据位置 dataSize, // 输入数据大小 resultPtr, // 返回数据位置 0x40 // 返回数据大小上限 ) // 获取返回数据大小 let resultSize := returndatasize() // 复制返回数据 returndatacopy(resultPtr, 0, resultSize) // 更新空闲内存指针 mstore(0x40, add(resultPtr, resultSize)) // 设置 result 指针和长度 mstore(result, resultSize) mstore(add(result, 0x20), resultPtr) } } // 发送 ETH function sendETH(address _to, uint256 _amount) external returns (bool) { assembly { // 使用 call 发送 ETH let success := call( gas(), _to, _amount, // 发送的 ETH 数量 0, // 无输入数据 0, 0, // 不关心返回数据 0 ) mstore(0x00, success) return(0x00, 32) } } // 静态调用(不修改状态) function staticCall(address _target, bytes memory _data) external view returns (bool success, bytes memory result) { assembly { let dataPtr := add(_data, 0x20) let dataSize := mload(_data) let resultPtr := mload(0x40) success := staticcall( gas(), _target, dataPtr, dataSize, resultPtr, 0x40 ) let resultSize := returndatasize() returndatacopy(resultPtr, 0, resultSize) mstore(0x40, add(resultPtr, resultSize)) mstore(result, resultSize) mstore(add(result, 0x20), resultPtr) } }}委托调用contract DelegateCallExample { address public implementation; uint256 public value; function delegateToImplementation(bytes memory _data) external returns (bytes memory) { assembly { let dataPtr := add(_data, 0x20) let dataSize := mload(_data) let resultPtr := mload(0x40) // 使用 delegatecall let success := delegatecall( gas(), sload(0), // implementation 地址在 slot 0 dataPtr, dataSize, resultPtr, 0x40 ) let resultSize := returndatasize() returndatacopy(resultPtr, 0, resultSize) // 检查调用是否成功 if iszero(success) { revert(resultPtr, resultSize) } return(resultPtr, resultSize) } }}6. Gas 优化技巧常见优化模式contract GasOptimizations { uint256[] public items; // 优化前:使用 Solidity 高级语法 function sumSolidity() external view returns (uint256 total) { for (uint i = 0; i < items.length; i++) { total += items[i]; } } // 优化后:使用 Assembly function sumAssembly() external view returns (uint256 total) { assembly { // 获取数组存储位置 mstore(0x00, items.slot) let baseSlot := keccak256(0x00, 0x20) // 获取数组长度 let length := sload(items.slot) // 遍历数组 for { let i := 0 } lt(i, length) { i := add(i, 1) } { let slot := add(baseSlot, i) let value := sload(slot) total := add(total, value) } mstore(0x00, total) return(0x00, 32) } } // 批量操作优化 function batchTransfer(address[] memory _recipients, uint256[] memory _amounts) external payable { require(_recipients.length == _amounts.length, "Length mismatch"); assembly { let recipientsPtr := add(_recipients, 0x20) let amountsPtr := add(_amounts, 0x20) let length := mload(_recipients) for { let i := 0 } lt(i, length) { i := add(i, 1) } { let recipient := mload(add(recipientsPtr, mul(i, 0x20))) let amount := mload(add(amountsPtr, mul(i, 0x20))) // 使用 call 发送 ETH let success := call( gas(), recipient, amount, 0, 0, 0, 0 ) if iszero(success) { revert(0, 0) } } } } // 短路求和优化 function optimizedSum(uint256[] memory _values) external pure returns (uint256) { assembly { let ptr := add(_values, 0x20) let length := mload(_values) let total := 0 // 使用 unchecked 的加法(Solidity 0.8.0+) for { let i := 0 } lt(i, length) { i := add(i, 1) } { total := add(total, mload(add(ptr, mul(i, 0x20)))) } mstore(0x00, total) return(0x00, 32) } }}7. 高级技巧创建合约contract ContractCreation { function deploy(bytes memory _bytecode) external returns (address addr) { assembly { let size := mload(_bytecode) let ptr := add(_bytecode, 0x20) // 使用 create 部署合约 addr := create(0, ptr, size) // 检查部署是否成功 if iszero(extcodesize(addr)) { revert(0, 0) } } } // 使用 create2 部署(确定性地址) function deployWithSalt(bytes memory _bytecode, bytes32 _salt) external returns (address addr) { assembly { let size := mload(_bytecode) let ptr := add(_bytecode, 0x20) addr := create2(0, ptr, size, _salt) if iszero(extcodesize(addr)) { revert(0, 0) } } } // 计算 create2 地址 function computeAddress( bytes32 _salt, bytes32 _bytecodeHash, address _deployer ) external pure returns (address) { assembly { // 地址 = 最后 20 字节 of keccak256(0xff + deployer + salt + bytecodeHash) mstore(0x00, _deployer) mstore(0x20, _salt) mstore(0x40, _bytecodeHash) let hash := keccak256(0x00, 0x60) mstore(0x00, hash) return(12, 20) // 返回最后 20 字节 } }}内联汇编与 Solidity 交互contract AssemblyInteraction { struct User { uint256 id; address wallet; uint256 balance; } mapping(uint256 => User) public users; // 使用 Assembly 读取结构体 function getUserAssembly(uint256 _id) external view returns (User memory user) { assembly { // 计算 mapping 的存储位置 mstore(0x00, _id) mstore(0x20, users.slot) let baseSlot := keccak256(0x00, 0x40) // 读取结构体的各个字段 // User 结构体占用 3 个 slot let id := sload(baseSlot) let wallet := sload(add(baseSlot, 1)) let balance := sload(add(baseSlot, 2)) // 存储到内存返回 let ptr := mload(0x40) mstore(ptr, id) mstore(add(ptr, 0x20), wallet) mstore(add(ptr, 0x40), balance) // 更新空闲内存指针 mstore(0x40, add(ptr, 0x60)) // 设置返回值 mstore(user, 0x60) // 指向内存位置 mstore(add(user, 0x20), ptr) } } // 条件判断优化 function optimizedCondition(uint256 x) external pure returns (uint256) { assembly { // 使用 switch 进行多条件判断 switch x case 0 { mstore(0x00, 100) } case 1 { mstore(0x00, 200) } default { mstore(0x00, 300) } return(0x00, 32) } }}8. 安全注意事项contract AssemblySafety { /* 安全注意事项: 1. 内存管理:确保正确更新空闲内存指针 2. 存储冲突:避免覆盖重要存储槽位 3. 溢出检查:Assembly 不自动检查溢出 4. 调用验证:检查外部调用返回值 5. 重入防护:手动实现重入锁 */ bool private locked; modifier noReentrant() { require(!locked, "Reentrant call"); locked = true; _; locked = false; } // 安全的 ETH 转账 function safeTransferETH(address _to, uint256 _amount) external noReentrant { assembly { // 检查地址是否有效 if iszero(_to) { revert(0, 0) } // 执行转账 let success := call( gas(), _to, _amount, 0, 0, 0, 0 ) // 验证结果 if iszero(success) { revert(0, 0) } } } // 防止整数溢出 function safeAdd(uint256 a, uint256 b) external pure returns (uint256) { assembly { let result := add(a, b) // 检查溢出:如果 result < a,说明溢出 if lt(result, a) { revert(0, 0) } mstore(0x00, result) return(0x00, 32) } } // 安全的存储写入 function safeStorageWrite(uint256 slot, uint256 value) external { assembly { // 检查 slot 是否在安全范围内 if lt(slot, 100) { revert(0, 0) } sstore(slot, value) } }}9. 常见用例contract AssemblyUseCases { // 1. 高效计算哈希 function efficientHash(bytes memory _data) external pure returns (bytes32) { assembly { let ptr := add(_data, 0x20) let size := mload(_data) let hash := keccak256(ptr, size) mstore(0x00, hash) return(0x00, 32) } } // 2. 检查合约代码 function hasCode(address _addr) external view returns (bool) { assembly { let size := extcodesize(_addr) mstore(0x00, gt(size, 0)) return(0x00, 32) } } // 3. 获取当前合约地址 function currentAddress() external view returns (address) { assembly { mstore(0x00, address()) return(0x00, 32) } } // 4. 获取调用者 function getCaller() external view returns (address) { assembly { mstore(0x00, caller()) return(0x00, 32) } } // 5. 获取调用数据 function getCallData() external pure returns (bytes32) { assembly { // 获取函数选择器(前 4 字节) let selector := calldataload(0) mstore(0x00, selector) return(0x00, 32) } } // 6. 高效数组操作 function findAndRemove(uint256[] storage _arr, uint256 _value) external { assembly { // 获取数组长度 let length := sload(_arr.slot) // 查找元素 mstore(0x00, _arr.slot) let baseSlot := keccak256(0x00, 0x20) for { let i := 0 } lt(i, length) { i := add(i, 1) } { let slot := add(baseSlot, i) if eq(sload(slot), _value) { // 找到元素,用最后一个元素替换 let lastSlot := add(baseSlot, sub(length, 1)) sstore(slot, sload(lastSlot)) // 减少长度 sstore(_arr.slot, sub(length, 1)) // 停止循环 stop() } } } }}10. 总结Assembly 是强大的底层工具,但需要谨慎使用:使用场景:Gas 敏感的关键路径实现 Solidity 不支持的功能精细控制内存和存储最佳实践:仅在必要时使用 Assembly充分测试和审计添加详细注释考虑使用 Yul(更安全的汇编方言)安全要点:正确管理内存指针验证所有外部调用手动检查溢出避免覆盖关键存储性能权衡:Assembly 可以显著节省 Gas但会增加代码复杂性和维护成本需要权衡可读性和性能
阅读 0·3月6日 23:07

cURL 和 wget 有什么区别?如何选择使用?

cURL 和 wget 都是命令行下载工具,但它们的设计理念、功能特性和使用场景有很大不同。了解这些差异有助于在实际工作中选择合适的工具。核心区别对比| 特性 | cURL | wget || ------------- | ----------- | ----------------- || 设计目标 | 数据传输工具 | 文件下载工具 || 协议支持 | 20+ 种协议 | 主要 HTTP/HTTPS/FTP || 双向传输 | ✅ 支持上传和下载 | ❌ 主要下载 || API 测试 | ✅ 非常适合 | ❌ 不适合 || 递归下载 | ❌ 不支持 | ✅ 支持网站镜像 || 断点续传 | ✅ 支持 | ✅ 支持 || 后台下载 | ❌ 需配合 nohup | ✅ 原生支持 || 配置文件 | ✅ .curlrc | ✅ .wgetrc || Cookie 支持 | ✅ 完整支持 | ✅ 支持 || 输出格式 | 灵活可控 | 简洁直观 |功能详细对比1. 协议支持# cURL 支持多种协议curl https://api.example.com # HTTPScurl ftp://ftp.example.com/file # FTPcurl sftp://sftp.example.com/file # SFTPcurl scp://host.example.com/file # SCPcurl smtp://mail.example.com # SMTPcurl ldap://ldap.example.com # LDAP# wget 主要支持 HTTP/HTTPS/FTPwget https://example.com/file.zip # HTTPSwget ftp://ftp.example.com/file # FTP2. 上传能力# cURL 支持上传curl -T file.txt ftp://ftp.example.com/curl -F "file=@upload.pdf" https://api.example.com/upload# wget 不支持上传# 需要使用其他工具如 ftp、scp3. API 交互# cURL 非常适合 API 测试curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token" \ -d '{"name":"test"}'# wget 不太适合 API 测试wget --post-data='name=test' https://api.example.com/users# 功能有限,不支持复杂 Header4. 递归下载# wget 擅长递归下载整个网站wget --mirror --convert-links --adjust-extension \ --page-requisites --no-parent \ https://example.com/# cURL 不支持递归下载# 需要配合其他工具或脚本使用场景对比选择 cURL 的场景# 1. API 开发和测试curl -v https://api.example.com/userscurl -X POST -H "Content-Type: application/json" -d '{}' https://api.example.com/users# 2. 需要上传文件curl -F "file=@upload.pdf" https://api.example.com/upload# 3. 复杂的 HTTP 操作curl -X PATCH -H "Authorization: Bearer token" https://api.example.com/users/1# 4. 多协议支持需求curl sftp://user@host.com/filecurl smtp://mail.example.com# 5. 需要灵活控制输出curl -w "%{http_code}\n" -o /dev/null -s https://api.example.com选择 wget 的场景# 1. 下载整个网站wget --mirror https://example.com/# 2. 后台下载大文件wget -b https://example.com/large-file.zip# 3. 批量下载wget -i urls.txt# 4. 遵循 robots.txt 的礼貌下载wget --recursive --level=1 https://example.com/# 5. 简单的文件下载wget https://example.com/file.zip命令对比示例| 任务 | cURL | wget || ------- | ------------------------------------------------- | ------------------------------------------------- || 简单下载 | curl -O https://example.com/file.zip | wget https://example.com/file.zip || 指定文件名 | curl -o myfile.zip https://example.com/file.zip | wget -O myfile.zip https://example.com/file.zip || 断点续传 | curl -C - -O https://example.com/file.zip | wget -c https://example.com/file.zip || 限速下载 | curl --limit-rate 1M -O file.zip | wget --limit-rate=1m file.zip || 后台下载 | nohup curl -O file.zip & | wget -b file.zip || 跟随重定向 | curl -L https://example.com | wget https://example.com (默认跟随) || 递归下载 | ❌ 不支持 | wget -r https://example.com || POST 请求 | curl -X POST -d 'data' URL | wget --post-data='data' URL |性能对比# 测试下载速度# cURLtime curl -s -o /dev/null https://example.com/large-file.zip# wgettime wget -q -O /dev/null https://example.com/large-file.zip# 结论:两者下载性能相当,差异主要在功能特性实际项目选择建议Web 开发/后端开发推荐 cURL,原因:API 测试和调试需要发送各种 HTTP 方法需要设置复杂的请求头需要处理 JSON 数据需要上传文件# 日常开发示例# 测试 APIcurl -X POST https://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"test"}'# 查看响应头curl -I https://api.example.com/health# 性能测试curl -w "@curl-format.txt" -o /dev/null -s https://api.example.com运维/系统管理推荐 wget,原因:下载软件包和文件后台下载任务网站备份和镜像批量下载# 运维场景示例# 下载软件包wget https://dl.example.com/software-1.0.tar.gz# 后台下载wget -b https://example.com/backup.tar.gz# 网站镜像wget --mirror --convert-links https://internal-docs.example.com/数据抓取/爬虫推荐 wget,原因:递归下载能力遵循 robots.txt自动处理链接转换# 数据抓取示例wget --recursive --no-clobber --page-requisites \ --html-extension --convert-links \ --restrict-file-names=windows \ --domains example.com \ --no-parent https://example.com/data/组合使用在实际工作中,两者可以配合使用:#!/bin/bash# 组合使用示例# 使用 cURL 获取下载链接DOWNLOAD_URL=$(curl -s https://api.example.com/releases/latest | jq -r '.download_url')# 使用 wget 下载文件(支持后台和断点续传)wget -c -b "$DOWNLOAD_URL" -O latest-release.tar.gz# 使用 cURL 验证下载结果curl -I "file://$(pwd)/latest-release.tar.gz"总结| 场景 | 推荐工具 | 理由 || -------- | -------- | ----------- || API 开发测试 | cURL | 功能全面,控制精细 || 文件下载 | wget | 简单易用,后台支持 || 网站镜像 | wget | 递归下载能力强 || 多协议传输 | cURL | 支持 20+ 协议 || 脚本自动化 | cURL | 输出灵活可控 || 批量下载 | wget | 支持从文件读取 URL |建议:两者都学习和掌握,根据具体场景选择最合适的工具。
阅读 0·3月6日 23:07

cURL 的 -v、-i、-I、-s 参数有什么区别?

cURL 提供了多个输出控制参数,用于控制请求的详细程度和输出格式。理解这些参数的区别对于调试和日常使用非常重要。参数对比表| 参数 | 全称 | 作用 | 输出内容 || ---- | -------------- | ----- | ------------- || -v | --verbose | 详细模式 | 完整请求/响应信息 || -i | --include | 包含响应头 | 响应头 + 响应体 || -I | --head | 仅请求头 | 仅响应头(HEAD 请求) || -s | --silent | 静默模式 | 仅响应体(无进度) || -S | --show-error | 显示错误 | 配合 -s 使用 |详细说明和示例1. -v(verbose)详细模式# 显示完整的请求和响应过程curl -v https://api.example.com# 输出示例:# * Trying 192.168.1.1:443...# * Connected to api.example.com (192.168.1.1) port 443 (#0)# > GET / HTTP/1.1# > Host: api.example.com# > User-Agent: curl/7.68.0# > Accept: */*# > # * Mark bundle as not supporting multiuse# < HTTP/1.1 200 OK# < Content-Type: application/json# < Content-Length: 123# < # {"status":"ok","data":[]}# * Connection #0 to host api.example.com left intact输出符号说明:* - 连接和协议信息> - 发送的请求数据< - 接收的响应数据{ 或 } - SSL/TLS 握手信息2. -i(include)包含响应头# 显示响应头和响应体curl -i https://api.example.com# 输出示例:# HTTP/1.1 200 OK# Content-Type: application/json# Content-Length: 123# Date: Mon, 01 Mar 2026 10:00:00 GMT# # {"status":"ok","data":[]}3. -I(head)仅响应头# 发送 HEAD 请求,仅获取响应头curl -I https://api.example.com# 输出示例:# HTTP/1.1 200 OK# Content-Type: application/json# Content-Length: 123# Date: Mon, 01 Mar 2026 10:00:00 GMT# Last-Modified: Sun, 28 Feb 2026 08:00:00 GMT# ETag: "abc123"# Cache-Control: max-age=3600注意: -I 会自动发送 HEAD 请求,不会获取响应体。4. -s(silent)静默模式# 静默模式,不显示进度信息curl -s https://api.example.com# 仅输出响应体内容# {"status":"ok","data":[]}# 配合 -S 显示错误信息curl -s -S https://api.example.com# 配合 -o 输出到文件curl -s https://api.example.com -o output.json组合使用# 静默 + 显示错误curl -s -S https://api.example.com# 详细 + 输出到文件curl -v https://api.example.com -o output.json 2>debug.log# 包含响应头 + 静默curl -i -s https://api.example.com# 仅响应头 + 格式化curl -I -s https://api.example.com | grep -i content-type实际应用场景场景 1:API 调试# 查看完整的请求响应过程curl -v -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"test"}'场景 2:检查响应头# 检查缓存头curl -I -s https://cdn.example.com/image.jpg | grep -i cache# 检查 Content-Typecurl -I -s https://api.example.com/data | grep -i content-type场景 3:脚本中使用# 静默获取数据并解析response=$(curl -s https://api.example.com/users)echo "$response" | jq '.[] | .name'# 检查 HTTP 状态码status=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com)if [ "$status" = "200" ]; then echo "OK"else echo "Error: $status"fi场景 4:调试 SSL 问题# 查看 SSL 握手过程curl -v https://api.example.com 2>&1 | grep -E "(SSL|TLS|certificate)"# 保存详细日志用于分析curl -v https://api.example.com 2>ssl-debug.log其他相关参数# --trace-ascii 保存详细跟踪信息curl --trace-ascii debug.txt https://api.example.com# --trace-time 添加时间戳curl -v --trace-time https://api.example.com# -w 自定义输出格式curl -s -o /dev/null -w "Status: %{http_code}\nTime: %{time_total}s\n" https://api.example.com# --progress-bar 显示进度条curl --progress-bar -O https://example.com/large-file.zip总结建议| 场景 | 推荐参数 || ----- | -------------------------- || 日常调试 | -v || 查看响应头 | -I 或 -i || 脚本中使用 | -s 或 -s -S || 性能测试 | -s -o /dev/null -w ... || 下载文件 | -s -O 或 --progress-bar |​
阅读 0·3月6日 23:06

cURL 支持哪些协议?如何使用 FTP/SFTP 传输文件?

cURL 是一个多协议数据传输工具,支持超过 20 种协议。了解这些协议的支持情况对于选择合适的传输方式非常重要。cURL 支持的协议| 协议 | 用途 | 示例 URL || ---------- | ------- | ----------------------------- || HTTP/HTTPS | Web 请求 | http://example.com || FTP/FTPS | 文件传输 | ftp://ftp.example.com || SFTP | 安全文件传输 | sftp://user@host.com || SCP | 安全复制 | scp://user@host.com/file || TFTP | 简单文件传输 | tftp://host.com/file || LDAP/LDAPS | 目录服务 | ldap://ldap.example.com || SMTP/SMTPS | 邮件发送 | smtp://mail.example.com || POP3/POP3S | 邮件接收 | pop3://mail.example.com || IMAP/IMAPS | 邮件访问 | imap://mail.example.com || RTSP/RTMP | 流媒体 | rtsp://stream.example.com || FILE | 本地文件 | file:///path/to/file || GOPHER | 早期互联网协议 | gopher://gopher.example.com || DICT | 字典协议 | dict://dict.org/d:word || TELNET | 远程登录 | telnet://host.com |FTP 文件传输FTP 下载# 匿名 FTP 下载curl ftp://ftp.example.com/pub/file.txt -o file.txt# 认证 FTP 下载curl -u "username:password" \ ftp://ftp.example.com/remote/file.txt \ -o file.txt# 下载目录列表curl ftp://ftp.example.com/pub/# 被动模式 FTPcurl --ftp-skip-pasv-ip \ ftp://ftp.example.com/file.txtFTP 上传# 上传文件到 FTPcurl -u "username:password" \ -T local-file.txt \ ftp://ftp.example.com/remote/path/# 上传并指定文件名curl -u "username:password" \ -T local-file.txt \ ftp://ftp.example.com/remote/renamed-file.txt# 创建目录并上传curl -u "username:password" \ --ftp-create-dirs \ -T file.txt \ ftp://ftp.example.com/new-folder/file.txtFTP 高级操作# 删除远程文件curl -u "username:password" \ -Q "DELE remote-file.txt" \ ftp://ftp.example.com/# 重命名文件curl -u "username:password" \ -Q "RNFR old-name.txt" \ -Q "RNTO new-name.txt" \ ftp://ftp.example.com/# 列出详细文件信息curl -u "username:password" \ ftp://ftp.example.com/ -l# 使用 FTPS(FTP over SSL)curl -u "username:password" \ ftps://ftp.example.com/file.txt# 主动模式 FTPcurl --ftp-port - \ -u "username:password" \ ftp://ftp.example.com/file.txtSFTP 文件传输SFTP 下载# 基本 SFTP 下载curl -u "username:password" \ sftp://sftp.example.com/remote/file.txt \ -o file.txt# 使用密钥认证(推荐)curl -u "username:" \ --key ~/.ssh/id_rsa \ --pubkey ~/.ssh/id_rsa.pub \ sftp://sftp.example.com/remote/file.txt \ -o file.txt# 指定端口curl -u "username:password" \ sftp://sftp.example.com:2222/remote/file.txt# 下载目录(需要配合 tar)ssh user@sftp.example.com "tar czf - /remote/folder" | tar xzf -SFTP 上传# 上传文件到 SFTPcurl -u "username:password" \ -T local-file.txt \ sftp://sftp.example.com/remote/path/# 使用密钥上传curl -u "username:" \ --key ~/.ssh/id_rsa \ -T local-file.txt \ sftp://sftp.example.com/remote/# 上传多个文件curl -u "username:password" \ -T "file1.txt" \ -T "file2.txt" \ sftp://sftp.example.com/remote/SCP 文件传输# SCP 下载curl -u "username:password" \ scp://host.example.com/remote/file.txt \ -o file.txt# SCP 上传curl -u "username:password" \ -T local-file.txt \ scp://host.example.com/remote/# 使用密钥curl -u "username:" \ --key ~/.ssh/id_rsa \ scp://host.example.com/remote/file.txt协议对比| 特性 | FTP | FTPS | SFTP | SCP || ----- | --- | ------ | ----- | ----- || 加密 | ❌ | ✅ | ✅ | ✅ || 端口 | 21 | 21/990 | 22 | 22 || 认证 | 密码 | 密码/证书 | 密码/密钥 | 密码/密钥 || 防火墙友好 | ❌ | ⚠️ | ✅ | ✅ || 推荐场景 | 内网 | 企业环境 | 通用 | 简单传输 |邮件协议示例# SMTP 发送邮件curl -v smtp://smtp.gmail.com:587 \ --mail-from "sender@example.com" \ --mail-rcpt "recipient@example.com" \ --upload-file email.txt \ --user "username:password"# POP3 读取邮件curl -u "username:password" \ pop3://pop.gmail.com# IMAP 列出邮箱curl -u "username:password" \ imap://imap.gmail.com实用脚本#!/bin/bash# FTP/SFTP 自动备份脚本SOURCE="/local/path/to/backup"DEST="sftp://backup.example.com/backups/"USER="backup_user"KEY="~/.ssh/backup_key"DATE=$(date +%Y%m%d)ARCHIVE="backup-${DATE}.tar.gz"# 创建压缩包tar czf "/tmp/${ARCHIVE}" -C "$(dirname $SOURCE)" "$(basename $SOURCE)"# 上传到 SFTPcurl -u "${USER}:" \ --key "$KEY" \ -T "/tmp/${ARCHIVE}" \ "${DEST}${ARCHIVE}"# 清理本地临时文件rm "/tmp/${ARCHIVE}"echo "Backup completed: ${ARCHIVE}"常见问题# 问题 1:FTP 被动模式失败# 解决:使用 --ftp-skip-pasv-ipcurl --ftp-skip-pasv-ip ftp://ftp.example.com/file# 问题 2:SFTP 密钥权限错误# 解决:确保私钥权限正确chmod 600 ~/.ssh/id_rsa# 问题 3:SSL 证书验证失败# 解决:指定 CA 或跳过验证(测试用)curl --cacert /path/to/ca.crt ftps://ftp.example.com/filecurl -k ftps://ftp.example.com/file # 不安全# 问题 4:文件名包含空格# 解决:使用 URL 编码或引号curl -u "user:pass" "ftp://ftp.example.com/file%20name.txt"​
阅读 0·3月6日 23:06

如何使用 cURL 编写自动化测试脚本?

cURL 是编写 API 自动化测试脚本的强大工具,配合 Shell 脚本可以实现完整的测试流程,包括测试执行、结果验证和报告生成。基础测试脚本结构#!/bin/bash# 基础 API 测试脚本API_BASE="https://api.example.com/v1"TOKEN="your_bearer_token"PASSED=0FAILED=0# 测试函数run_test() { local name="$1" local expected_status="$2" shift 2 echo "Running test: $name" # 执行请求并获取状态码 status=$(curl -s -o /dev/null -w "%{http_code}" "$@") if [ "$status" = "$expected_status" ]; then echo "✅ PASS: $name (Status: $status)" ((PASSED++)) else echo "❌ FAIL: $name (Expected: $expected_status, Got: $status)" ((FAILED++)) fi}# 执行测试run_test "GET users" 200 -H "Authorization: Bearer $TOKEN" "$API_BASE/users"run_test "GET user by ID" 200 -H "Authorization: Bearer $TOKEN" "$API_BASE/users/1"run_test "POST create user" 201 -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"name":"test"}' "$API_BASE/users"run_test "DELETE user" 204 -X DELETE -H "Authorization: Bearer $TOKEN" "$API_BASE/users/999"# 输出结果echo ""echo "Test Results:"echo " Passed: $PASSED"echo " Failed: $FAILED"echo " Total: $((PASSED + FAILED))"exit $FAILED响应内容验证#!/bin/bash# 响应内容验证测试API_BASE="https://api.example.com/v1"TOKEN="your_bearer_token"# JSON 验证函数assert_json_field() { local response="$1" local field="$2" local expected="$3" actual=$(echo "$response" | jq -r "$field") if [ "$actual" = "$expected" ]; then echo "✅ PASS: Field $field = $expected" return 0 else echo "❌ FAIL: Field $field expected '$expected', got '$actual'" return 1 fi}# 测试用户 APIecho "Testing User API..."# 创建用户response=$(curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"张三","email":"zhangsan@example.com"}' \ "$API_BASE/users")# 验证响应assert_json_field "$response" ".name" "张三"assert_json_field "$response" ".email" "zhangsan@example.com"assert_json_field "$response" ".status" "active"# 验证响应包含特定字段if echo "$response" | jq -e '.id' > /dev/null; then echo "✅ PASS: Response contains 'id' field"else echo "❌ FAIL: Response missing 'id' field"fi性能测试脚本#!/bin/bash# API 性能测试脚本API_URL="https://api.example.com/v1/users"TOKEN="your_bearer_token"CONCURRENT=10REQUESTS=100# 单请求性能测试echo "Single Request Performance:"curl -w "DNS Lookup: %{time_namelookup}sTCP Connect: %{time_connect}sSSL Handshake: %{time_appconnect}sTTFB: %{time_starttransfer}sTotal Time: %{time_total}sSize: %{size_download} bytesSpeed: %{speed_download} bytes/s" \ -o /dev/null -s \ -H "Authorization: Bearer $TOKEN" \ "$API_URL"# 并发性能测试echo ""echo "Concurrent Load Test ($CONCURRENT concurrent, $REQUESTS total):"start_time=$(date +%s)# 使用 xargs 进行并发请求seq $REQUESTS | xargs -P $CONCURRENT -I {} \ curl -s -o /dev/null -w "%{http_code},%{time_total}\n" \ -H "Authorization: Bearer $TOKEN" \ "$API_URL" > results.txtend_time=$(date +%s)duration=$((end_time - start_time))# 分析结果echo "Results Analysis:"echo " Total Time: ${duration}s"echo " Requests/sec: $(echo "scale=2; $REQUESTS / $duration" | bc)"echo " Success Rate: $(grep -c "^200" results.txt) / $REQUESTS"echo " Avg Response Time: $(awk -F',' '{sum+=$2; count++} END {print sum/count}' results.txt)s"rm results.txt集成测试流程#!/bin/bash# 完整集成测试流程set -e # 遇到错误立即退出API_BASE="https://api.example.com/v1"TOKEN="your_bearer_token"TEST_USER_ID=""echo "========== API Integration Tests =========="# 1. 健康检查echo "1. Health Check"curl -sf "$API_BASE/health" | jq -e '.status == "ok"'echo "✅ Health check passed"# 2. 创建测试数据echo ""echo "2. Create Test User"response=$(curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Test User","email":"test@example.com"}' \ "$API_BASE/users")TEST_USER_ID=$(echo "$response" | jq -r '.id')echo "✅ Created user with ID: $TEST_USER_ID"# 3. 验证创建echo ""echo "3. Verify User Creation"curl -sf -H "Authorization: Bearer $TOKEN" \ "$API_BASE/users/$TEST_USER_ID" | jq -e '.name == "Test User"'echo "✅ User verified"# 4. 更新操作echo ""echo "4. Update User"curl -s -X PATCH \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Updated Name"}' \ "$API_BASE/users/$TEST_USER_ID" | jq -e '.name == "Updated Name"'echo "✅ User updated"# 5. 删除测试数据echo ""echo "5. Cleanup"curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \ "$API_BASE/users/$TEST_USER_ID"echo "✅ Test user deleted"echo ""echo "========== All Tests Passed =========="测试报告生成#!/bin/bash# 生成 HTML 测试报告API_BASE="https://api.example.com/v1"TOKEN="your_bearer_token"REPORT_FILE="test-report.html"# 初始化报告cat > "$REPORT_FILE" << 'EOF'<!DOCTYPE html><html><head> <title>API Test Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .pass { color: green; } .fail { color: red; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #4CAF50; color: white; } </style></head><body> <h1>API Test Report</h1> <p>Generated: $(date)</p> <table> <tr> <th>Test Name</th> <th>Method</th> <th>Status</th> <th>Duration</th> <th>Result</th> </tr>EOF# 执行测试并记录结果run_test() { local name="$1" local method="$2" local url="$3" shift 3 start=$(date +%s.%N) response=$(curl -s -w "\n%{http_code}" "$@" "$url") end=$(date +%s.%N) http_code=$(echo "$response" | tail -1) duration=$(echo "$end - $start" | bc) if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then result="<span class='pass'>PASS</span>" else result="<span class='fail'>FAIL</span>" fi echo "<tr> <td>$name</td> <td>$method</td> <td>$http_code</td> <td>${duration}s</td> <td>$result</td> </tr>" >> "$REPORT_FILE"}# 执行测试run_test "List Users" "GET" "$API_BASE/users" -H "Authorization: Bearer $TOKEN"run_test "Get User" "GET" "$API_BASE/users/1" -H "Authorization: Bearer $TOKEN"run_test "Create User" "POST" "$API_BASE/users" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"name":"test"}'# 完成报告echo "</table></body></html>" >> "$REPORT_FILE"echo "Report generated: $REPORT_FILE"CI/CD 集成#!/bin/bash# CI/CD 集成测试脚本API_BASE="${API_BASE:-https://api.example.com/v1}"TOKEN="${API_TOKEN}"EXIT_CODE=0# 颜色定义RED='\033[0;31m'GREEN='\033[0;32m'NC='\033[0m'# 测试执行器execute_test() { local description="$1" local command="$2" local expected="$3" echo "Testing: $description" result=$(eval "$command") if [ "$result" = "$expected" ]; then echo -e "${GREEN}✓ PASS${NC}" else echo -e "${RED}✗ FAIL${NC}" echo " Expected: $expected" echo " Got: $result" EXIT_CODE=1 fi}# 运行测试echo "========== Running API Tests =========="execute_test "API Health Check" \ "curl -s -o /dev/null -w '%{http_code}' $API_BASE/health" \ "200"execute_test "Authentication Required" \ "curl -s -o /dev/null -w '%{http_code}' $API_BASE/users" \ "401"execute_test "List Users with Auth" \ "curl -s -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer $TOKEN' $API_BASE/users" \ "200"# 输出结果echo ""if [ $EXIT_CODE -eq 0 ]; then echo -e "${GREEN}All tests passed!${NC}"else echo -e "${RED}Some tests failed!${NC}"fiexit $EXIT_CODE最佳实践模块化设计:将测试函数和配置分离错误处理:使用 set -e 或显式检查返回值日志记录:保存详细日志便于调试环境配置:使用环境变量管理不同环境报告生成:生成可读性强的测试报告CI 集成:确保脚本能在 CI/CD 环境中运行
阅读 0·3月6日 23:06

如何在 cURL 中发送和接收 JSON 数据?

在 cURL 中处理 JSON 数据 是 API 测试中最常见的场景。正确发送和接收 JSON 数据需要设置合适的请求头和使用正确的参数。发送 JSON 数据# 基本 JSON POST 请求curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"张三","email":"zhangsan@example.com"}'# 使用单引号包裹 JSON(推荐)curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"John","age":25}'# JSON 中包含引号时使用转义curl -X POST https://api.example.com/data \ -H "Content-Type: application/json" \ -d "{\"name\":\"John's Data\",\"value\":\"test\"}"# 从文件读取 JSON 数据curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d @user.json# 使用 heredoc 发送多行 JSONcurl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d @- <<EOF{ "name": "张三", "email": "zhangsan@example.com", "roles": ["admin", "user"], "settings": { "theme": "dark", "language": "zh-CN" }}EOF接收和处理 JSON 响应# 获取 JSON 响应curl https://api.example.com/users# 格式化 JSON 输出(使用 jq)curl -s https://api.example.com/users | jq# 提取特定字段curl -s https://api.example.com/users | jq '.name'# 仅显示响应体(静默模式)curl -s https://api.example.com/users# 保存 JSON 响应到文件curl -s https://api.example.com/users -o users.json# 显示响应头和格式化的 JSONcurl -i https://api.example.com/users | jq常见 JSON 操作场景# RESTful API 创建资源curl -X POST https://api.example.com/products \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -d '{ "name": "iPhone 15", "price": 7999, "stock": 100 }'# 更新资源(PUT)curl -X PUT https://api.example.com/products/123 \ -H "Content-Type: application/json" \ -d '{"price": 6999}'# 部分更新(PATCH)curl -X PATCH https://api.example.com/products/123 \ -H "Content-Type: application/json" \ -d '{"stock": 50}'# 批量操作curl -X POST https://api.example.com/products/batch \ -H "Content-Type: application/json" \ -d @products.json重要参数说明| 参数 | 作用 | 示例 || ------------------------------------- | ------------ | ---------------------- || -H "Content-Type: application/json" | 声明发送 JSON 格式 | 必须设置 || -d 或 --data | 发送数据 | -d '{"key":"value"}' || -d @filename | 从文件读取数据 | -d @data.json || -s 或 --silent | 静默模式 | 不显示进度信息 || -o | 输出到文件 | -o response.json |注意事项必须设置 Content-Type:服务器需要知道请求体是 JSON 格式引号处理:JSON 字符串建议用单引号包裹,内部使用双引号特殊字符:JSON 中的特殊字符需要正确转义编码问题:确保终端支持 UTF-8 编码以正确显示中文
阅读 0·3月6日 23:05

Solidity 中如何处理随机数生成?有哪些安全方案?

在 Solidity 中生成随机数是一个复杂的问题,因为区块链是确定性的环境,无法直接生成真正随机的数。以下是各种随机数生成方案及其安全性分析。1. 不安全的随机数生成方式使用区块哈希(不安全)contract InsecureRandom { // 危险:矿工可以操纵区块哈希 function getRandomNumber() public view returns (uint256) { return uint256(keccak256(abi.encodePacked(blockhash(block.number - 1)))); } // 危险:矿工可以操纵时间戳 function getRandomFromTimestamp() public view returns (uint256) { return uint256(keccak256(abi.encodePacked(block.timestamp))); } // 危险:所有参数都可被矿工操纵 function getRandomInsecure() public view returns (uint256) { return uint256(keccak256(abi.encodePacked( block.timestamp, block.difficulty, msg.sender ))); }}为什么这些方式不安全?矿工可以操纵 blockhash、block.timestamp、block.difficulty攻击者可以预测结果并选择性提交交易所有链上数据都是公开可见的2. Commit-Reveal 方案这是一种两阶段提交方案,可以防止前置交易攻击。contract CommitReveal { struct Commit { bytes32 commitHash; uint256 revealDeadline; bool revealed; } mapping(address => Commit) public commits; // 第一阶段:提交哈希 function commit(bytes32 _commitHash) external { require(commits[msg.sender].commitHash == bytes32(0), "Already committed"); commits[msg.sender] = Commit({ commitHash: _commitHash, revealDeadline: block.number + 10, revealed: false }); } // 第二阶段:揭示原始值 function reveal(uint256 _secret, uint256 _guess) external { Commit storage userCommit = commits[msg.sender]; require(userCommit.commitHash != bytes32(0), "No commit found"); require(!userCommit.revealed, "Already revealed"); require(block.number <= userCommit.revealDeadline, "Reveal period ended"); // 验证哈希 bytes32 revealHash = keccak256(abi.encodePacked(_secret, _guess)); require(revealHash == userCommit.commitHash, "Invalid reveal"); userCommit.revealed = true; // 生成随机数 uint256 randomNumber = uint256(keccak256(abi.encodePacked( _secret, blockhash(userCommit.revealDeadline) ))); // 使用随机数... } // 计算提交哈希的辅助函数 function getCommitHash(uint256 _secret, uint256 _guess) external pure returns (bytes32) { return keccak256(abi.encodePacked(_secret, _guess)); }}3. Chainlink VRF(可验证随机函数)Chainlink VRF 是目前最安全的链上随机数解决方案。import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";contract RandomNumberConsumer is VRFConsumerBaseV2 { VRFCoordinatorV2Interface COORDINATOR; // 订阅 ID uint64 s_subscriptionId; // 使用的 Gas 通道 bytes32 keyHash; // 回调 Gas 限制 uint32 callbackGasLimit = 100000; // 请求的确认数 uint16 requestConfirmations = 3; // 请求的随机数数量 uint32 numWords = 1; // 存储请求 ID 和对应的游戏数据 mapping(uint256 => address) public requestToGame; mapping(uint256 => uint256[]) public requestIdToRandomWords; event RandomWordsRequested(uint256 requestId, address game); event RandomWordsFulfilled(uint256 requestId, uint256[] randomWords); constructor( uint64 subscriptionId, address vrfCoordinator, bytes32 _keyHash ) VRFConsumerBaseV2(vrfCoordinator) { COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); s_subscriptionId = subscriptionId; keyHash = _keyHash; } // 请求随机数 function requestRandomWords() external returns (uint256 requestId) { requestId = COORDINATOR.requestRandomWords( keyHash, s_subscriptionId, requestConfirmations, callbackGasLimit, numWords ); requestToGame[requestId] = msg.sender; emit RandomWordsRequested(requestId, msg.sender); } // Chainlink 回调函数 function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { requestIdToRandomWords[requestId] = randomWords; emit RandomWordsFulfilled(requestId, randomWords); // 使用随机数处理游戏逻辑 address game = requestToGame[requestId]; // ... }}4. 使用预言机(Oracle)通过预言机获取链外随机数。interface IOracle { function requestRandomNumber() external returns (uint256 requestId); function getRandomNumber(uint256 requestId) external view returns (uint256);}contract OracleRandom { IOracle public oracle; mapping(uint256 => bool) public usedRandomNumbers; event RandomNumberRequested(uint256 requestId); event RandomNumberReceived(uint256 randomNumber); constructor(address _oracle) { oracle = IOracle(_oracle); } function requestRandom() external returns (uint256 requestId) { requestId = oracle.requestRandomNumber(); emit RandomNumberRequested(requestId); return requestId; } function useRandom(uint256 requestId) external returns (uint256) { uint256 randomNumber = oracle.getRandomNumber(requestId); require(!usedRandomNumbers[randomNumber], "Random number already used"); usedRandomNumbers[randomNumber] = true; emit RandomNumberReceived(randomNumber); return randomNumber; }}5. 多方计算(MPC)方案通过多个参与方共同生成随机数。contract MPCRandom { struct Contribution { bytes32 hash; uint256 value; bool revealed; } mapping(address => Contribution) public contributions; address[] public participants; uint256 public revealDeadline; bool public randomGenerated; uint256 public randomNumber; event ContributionSubmitted(address participant); event RandomNumberGenerated(uint256 randomNumber); modifier onlyParticipant() { require(isParticipant(msg.sender), "Not a participant"); _; } function isParticipant(address _addr) public view returns (bool) { for (uint i = 0; i < participants.length; i++) { if (participants[i] == _addr) return true; } return false; } // 第一阶段:提交贡献的哈希 function submitHash(bytes32 _hash) external onlyParticipant { require(block.number < revealDeadline, "Submission period ended"); require(contributions[msg.sender].hash == bytes32(0), "Already submitted"); contributions[msg.sender].hash = _hash; emit ContributionSubmitted(msg.sender); } // 第二阶段:揭示贡献值 function reveal(uint256 _value, uint256 _nonce) external onlyParticipant { require(block.number >= revealDeadline, "Reveal not started"); require(!contributions[msg.sender].revealed, "Already revealed"); bytes32 expectedHash = keccak256(abi.encodePacked(_value, _nonce)); require(expectedHash == contributions[msg.sender].hash, "Invalid reveal"); contributions[msg.sender].value = _value; contributions[msg.sender].revealed = true; } // 生成随机数 function generateRandom() external { require(block.number > revealDeadline + 10, "Wait for all reveals"); require(!randomGenerated, "Random already generated"); uint256 combined = 0; for (uint i = 0; i < participants.length; i++) { if (contributions[participants[i]].revealed) { combined ^= contributions[participants[i]].value; } } randomNumber = uint256(keccak256(abi.encodePacked(combined, blockhash(block.number - 1)))); randomGenerated = true; emit RandomNumberGenerated(randomNumber); }}6. 随机数使用场景NFT 随机铸造contract NFTRandomMint is VRFConsumerBaseV2 { // ... VRF 设置 ... struct MintRequest { address minter; uint256 tokenId; bool fulfilled; } mapping(uint256 => MintRequest) public mintRequests; uint256 public nextTokenId; function requestMint() external returns (uint256 requestId) { requestId = requestRandomWords(); mintRequests[requestId] = MintRequest({ minter: msg.sender, tokenId: nextTokenId++, fulfilled: false }); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { MintRequest storage request = mintRequests[requestId]; request.fulfilled = true; // 使用随机数确定 NFT 属性 uint256 randomness = randomWords[0]; uint256 rarity = randomness % 100; // 0-99 // 铸造 NFT _safeMint(request.minter, request.tokenId); // 设置属性 if (rarity < 5) { _setTokenRarity(request.tokenId, "Legendary"); } else if (rarity < 20) { _setTokenRarity(request.tokenId, "Epic"); } else if (rarity < 50) { _setTokenRarity(request.tokenId, "Rare"); } else { _setTokenRarity(request.tokenId, "Common"); } }}游戏随机结果contract DiceGame is VRFConsumerBaseV2 { struct Game { address player; uint256 bet; uint256 predictedNumber; bool fulfilled; uint256 result; } mapping(uint256 => Game) public games; event GameStarted(uint256 requestId, address player, uint256 bet); event GameFinished(uint256 requestId, uint256 result, bool won); function play(uint256 _predictedNumber) external payable returns (uint256 requestId) { require(msg.value >= 0.01 ether, "Minimum bet is 0.01 ETH"); require(_predictedNumber >= 1 && _predictedNumber <= 6, "Predict 1-6"); requestId = requestRandomWords(); games[requestId] = Game({ player: msg.sender, bet: msg.value, predictedNumber: _predictedNumber, fulfilled: false, result: 0 }); emit GameStarted(requestId, msg.sender, msg.value); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { Game storage game = games[requestId]; game.fulfilled = true; // 生成 1-6 的骰子结果 game.result = (randomWords[0] % 6) + 1; bool won = (game.result == game.predictedNumber); if (won) { // 支付奖金(6倍赔率) payable(game.player).transfer(game.bet * 6); } emit GameFinished(requestId, game.result, won); }}7. 方案对比| 方案 | 安全性 | 成本 | 延迟 | 适用场景 || ------------- | --- | -- | -- | ----- || 区块哈希 | 低 | 免费 | 即时 | 测试环境 || Commit-Reveal | 中 | 低 | 高 | 简单游戏 || Chainlink VRF | 高 | 中等 | 中等 | 生产环境 || 预言机 | 中 | 中等 | 中等 | 特定需求 || MPC | 高 | 高 | 高 | 高价值场景 |8. 最佳实践生产环境使用 Chainlink VRF:最安全、最可靠的解决方案避免使用链上数据生成随机数:所有链上数据都可被操纵实现重试机制:处理 VRF 回调失败的情况限制随机数使用范围:避免重复使用同一随机数添加时间锁:给用户提供退出时间// 安全的随机数使用模式contract SafeRandomUsage { uint256 public constant MAX_RANDOM = 10000; mapping(uint256 => bool) public usedRandoms; function useRandom(uint256 _random) internal returns (uint256) { uint256 normalized = _random % MAX_RANDOM; require(!usedRandoms[normalized], "Random already used"); usedRandoms[normalized] = true; return normalized; }}​
阅读 0·3月6日 23:05

cURL 如何处理大文件下载和断点续传?

处理大文件下载和断点续传是 cURL 的重要功能,可以有效管理带宽、节省时间和处理网络中断。基础下载功能# 基础下载(显示进度)curl -O https://example.com/large-file.zip# 指定输出文件名curl -o myfile.zip https://example.com/large-file.zip# 跟随重定向下载curl -L -O https://example.com/download/file# 静默下载curl -s -O https://example.com/large-file.zip断点续传断点续传是 cURL 最强大的功能之一,可以在下载中断后从断点继续。# 断点续传(-C - 自动检测断点)curl -C - -O https://example.com/large-file.zip# 指定偏移量续传(从第 1024 字节开始)curl -C 1024 -O https://example.com/large-file.zip# 完整示例:带重试的断点续传for i in {1..5}; do curl -C - -o large-file.zip https://example.com/large-file.zip && break echo "Attempt $i failed, retrying..." sleep 5done分块下载将大文件分成多个部分并行下载,可以显著提升下载速度。# 下载文件的特定范围(字节)# 下载前 1MBcurl -r 0-1048575 -o part1.zip https://example.com/large-file.zip# 下载第 2 个 1MBcurl -r 1048576-2097151 -o part2.zip https://example.com/large-file.zip# 下载剩余部分curl -r 2097152- -o part3.zip https://example.com/large-file.zip# 合并分块文件cat part1.zip part2.zip part3.zip > complete.zip# 删除分块文件rm part1.zip part2.zip part3.zip并行下载脚本#!/bin/bash# 并行分块下载脚本URL="https://example.com/large-file.zip"FILE="large-file.zip"CHUNKS=4FILE_SIZE=$(curl -sI "$URL" | grep -i content-length | awk '{print $2}' | tr -d '\r')CHUNK_SIZE=$((FILE_SIZE / CHUNKS))echo "File size: $FILE_SIZE bytes"echo "Chunk size: $CHUNK_SIZE bytes"# 并行下载各分块for i in $(seq 0 $((CHUNKS-1))); do START=$((i * CHUNK_SIZE)) if [ $i -eq $((CHUNKS-1)) ]; then END="" else END=$((START + CHUNK_SIZE - 1)) fi echo "Downloading chunk $i: $START-$END" curl -r "$START-$END" -o "${FILE}.part$i" "$URL" &done# 等待所有下载完成wait# 合并文件for i in $(seq 0 $((CHUNKS-1))); do cat "${FILE}.part$i" >> "$FILE" rm "${FILE}.part$i"doneecho "Download complete: $FILE"限速下载# 限制下载速度为 100KB/scurl --limit-rate 100K -O https://example.com/large-file.zip# 限制为 1MB/scurl --limit-rate 1M -O https://example.com/large-file.zip# 限速 + 断点续传curl --limit-rate 500K -C - -O https://example.com/large-file.zip下载进度控制# 显示进度条curl --progress-bar -O https://example.com/large-file.zip# 自定义进度显示curl -# -o large-file.zip https://example.com/large-file.zip# 静默下载(无进度)curl -s -o large-file.zip https://example.com/large-file.zip# 下载统计信息curl -w "\nDownloaded: %{size_download} bytes\nSpeed: %{speed_download} bytes/s\nTime: %{time_total}s\n" \ -o large-file.zip \ -s https://example.com/large-file.zip服务器端支持检测# 检查服务器是否支持断点续传curl -I https://example.com/large-file.zip | grep -i "accept-ranges"# 如果返回 Accept-Ranges: bytes,则支持断点续传# 检查文件大小curl -I https://example.com/large-file.zip | grep -i "content-length"下载恢复策略#!/bin/bash# 智能下载恢复脚本URL="https://example.com/large-file.zip"OUTPUT="large-file.zip"MAX_RETRIES=10RETRY_COUNT=0while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do echo "Download attempt $((RETRY_COUNT + 1))..." if curl -C - -o "$OUTPUT" "$URL"; then echo "Download completed successfully!" exit 0 else RETRY_COUNT=$((RETRY_COUNT + 1)) echo "Download failed, waiting to retry..." sleep $((RETRY_COUNT * 2)) # 指数退避 fidoneecho "Download failed after $MAX_RETRIES attempts"exit 1多线程下载工具对比| 特性 | cURL | wget | aria2 || ---------- | ---- | ---- | ----- || 断点续传 | ✅ | ✅ | ✅ || 多线程 | 需脚本 | ❌ | ✅ || 限速 | ✅ | ✅ | ✅ || 自动重试 | 需脚本 | ✅ | ✅ || BitTorrent | ❌ | ❌ | ✅ |最佳实践# 1. 始终使用断点续传下载大文件curl -C - -O https://example.com/large-file.zip# 2. 配合重试机制curl --retry 5 --retry-delay 2 -C - -O https://example.com/large-file.zip# 3. 限速避免占用全部带宽curl --limit-rate 2M -C - -O https://example.com/large-file.zip# 4. 验证下载完整性curl -o file.zip https://example.com/file.zipmd5sum file.zip# 对比服务器提供的 MD5# 5. 后台下载nohup curl -C - -o large-file.zip https://example.com/large-file.zip > download.log 2>&1 &常见问题解决# 问题 1:服务器不支持断点续传# 解决:只能重新下载,或使用支持多源的工具# 问题 2:下载速度过慢# 解决:使用分块并行下载脚本,或更换下载源# 问题 3:磁盘空间不足# 解决:检查磁盘空间,或使用流式处理df -h# 问题 4:下载被中断# 解决:使用 -C - 自动续传curl -C - -O https://example.com/large-file.zip# 问题 5:文件名乱码# 解决:使用 -o 指定文件名curl -o "$(echo -e 'filename.txt')" https://example.com/file​
阅读 0·3月6日 23:04

axios 和 fetch API 有什么区别?在什么场景下应该选择使用 axios?

Axios vs Fetch API 对比Axios 和 Fetch API 都是用于发送 HTTP 请求的工具,但它们在功能、易用性和兼容性方面存在显著差异。核心区别对比表| 特性 | Axios | Fetch API ||------|-------|-----------|| API 设计 | 基于 Promise,API 更友好 | 原生 Promise,API 较底层 || JSON 处理 | 自动转换 JSON 数据 | 需要手动调用 .json() || 请求拦截器 | ✅ 原生支持 | ❌ 需要自行封装 || 响应拦截器 | ✅ 原生支持 | ❌ 需要自行封装 || 请求取消 | ✅ 支持 AbortController | ✅ 支持 AbortController || 超时设置 | ✅ 原生支持 timeout | ❌ 需要手动实现 || 进度监控 | ✅ 原生支持 onUploadProgress/onDownloadProgress | ❌ 需要手动实现 || 错误处理 | HTTP 错误自动 reject | 只有网络错误才 reject || 浏览器兼容 | IE11+ | Chrome 42+, Edge 14+, Firefox 39+ || 体积 | ~13KB (gzip) | 原生支持,无额外体积 || Node.js 支持 | ✅ 支持 | ❌ 不支持(需 polyfill) |详细对比分析1. JSON 数据处理Axios(自动处理):// 自动将响应转换为 JSONconst response = await axios.get('/api/users');console.log(response.data); // 已经是 JavaScript 对象// 自动将请求体转换为 JSONawait axios.post('/api/users', { name: 'John' });Fetch(手动处理):// 需要手动调用 .json()const response = await fetch('/api/users');const data = await response.json(); // 额外的 awaitconsole.log(data);// 需要手动设置 headers 和 stringifyawait fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John' })});2. 错误处理Axios(自动处理 HTTP 错误):try { const response = await axios.get('/api/not-found');} catch (error) { // 404 会进入 catch console.log(error.response.status); // 404}Fetch(需要手动检查状态):const response = await fetch('/api/not-found');// 需要手动检查状态码if (!response.ok) { // 404 不会自动进入 catch throw new Error(`HTTP error! status: ${response.status}`);}3. 超时设置Axios(原生支持):axios.get('/api/data', { timeout: 5000 // 5秒超时});Fetch(需要手动实现):const fetchWithTimeout = (url, options = {}, timeout = 5000) => { return Promise.race([ fetch(url, options), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout) ) ]);};4. 拦截器Axios(原生支持):// 请求拦截器axios.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${token}`; return config;});// 响应拦截器axios.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { redirectToLogin(); } return Promise.reject(error); });Fetch(需要自行封装):// 需要创建包装函数const fetchWithAuth = (url, options = {}) => { return fetch(url, { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${token}` } });};5. 进度监控Axios(原生支持):axios.post('/api/upload', formData, { onUploadProgress: (progressEvent) => { const percent = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); console.log(`上传进度: ${percent}%`); }});Fetch(需要手动实现):// Fetch 没有原生进度支持,需要使用 ReadableStreamconst response = await fetch('/api/download');const reader = response.body.getReader();while (true) { const { done, value } = await reader.read(); if (done) break; // 手动计算进度}选择建议使用 Axios 的场景需要拦截器功能统一添加认证 Token统一错误处理统一日志记录需要进度监控文件上传下载大文件传输需要超时控制防止请求挂起提升用户体验项目复杂度较高多个 API 服务复杂的错误处理逻辑需要请求重试机制需要 IE 支持需要兼容 IE11Node.js 环境服务端渲染Node.js 脚本使用 Fetch 的场景追求最小体积对包体积敏感的项目简单的单页面应用现代浏览器环境不需要 IE 支持现代框架(Next.js, Remix 等)简单的 HTTP 请求不需要复杂的拦截器简单的 GET/POST 请求学习目的理解底层 HTTP API教学演示现代框架中的选择React/Vue/Angular 项目// 推荐使用 Axiosimport axios from 'axios';const api = axios.create({ baseURL: process.env.VUE_APP_API_URL, timeout: 10000});// 添加拦截器api.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config;});Next.js / Remix 项目// 可以使用 Fetch(配合框架的数据获取函数)// app/page.js (Next.js App Router)async function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } // 缓存配置 }); if (!res.ok) { throw new Error('Failed to fetch'); } return res.json();}总结| 场景 | 推荐工具 ||------|----------|| 企业级应用 | Axios || 需要拦截器 | Axios || 文件上传下载 | Axios || 需要超时控制 | Axios || 需要 IE 支持 | Axios || Node.js 环境 | Axios || 简单项目 | Fetch || 对体积敏感 | Fetch || 现代浏览器 | Fetch || 学习目的 | Fetch |一般建议:中大型项目、需要复杂 HTTP 处理 → 选择 Axios小型项目、简单请求、追求轻量 → 选择 Fetch
阅读 0·3月6日 23:04