部署在Heroku的Vapor 1.5服务器JSON序列化错误求助
问题情况
我把Vapor 1.5的服务器部署到Heroku后踩了个坑:调用线上路由时总是报JSON序列化错误,提示数据格式不对,但本地跑http://0.0.0.0:8080/my/route完全没问题。Heroku上用的是Swift 3.1,错误详情和服务器日志我都整理在下面了,求解决思路!
错误详情
错误代码:4
错误信息:
JSON could not be serialized because of error: The data couldn’t be read because it isn’t in the correct format.
服务器返回的错误页面:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <title>Application Error</title> <style media="screen"> html,body,iframe { margin: 0; padding: 0; } html,body { height: 100%; overflow: hidden; } iframe { width: 100%; height: 100%; border: 0; } </style> </head> <body> <iframe src="//www.herokucdn.com/error-pages/application-error.html"></iframe> </body> </html>
Alamofire错误栈:
Alamofire.AFError.responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
服务器日志
remote: -----> Compressing... remote: Done: 93.2M remote: -----> Launching... remote: Released v13 remote: https://secure-garden-28988.herokuapp.com/ deployed to Heroku remote: remote: ! Warning: You are running on a deprecated stack. remote: ! Please upgrade to the latest stack by following the instructions on: remote: ! https://devcenter.heroku.com/articles/upgrading-to-the-latest-stack remote: remote: Verifying deploy... done. To https://git.heroku.com/secure-garden-28988.git c44026d..144abef master -> master MacBook-Pro:StripePayment bogdanbarbulescu$ heroku logs 2018-05-30T12:03:01.197383+00:00 heroku[web.1]: Starting process with command `App --env=production --workdir=./ --config:servers.default.port=12878` 2018-05-30T12:03:03.094008+00:00 app[web.1]: Production mode enabled, disabling informational logs. 2018-05-30T12:03:03.116578+00:00 app[web.1]: No command supplied, defaulting to serve... 2018-05-30T12:03:03.116707+00:00 app[web.1]: No preparations. 2018-05-30T12:03:03.117882+00:00 app[web.1]: Server 'default' starting at 0.0.0.0:12878 2018-05-30T12:03:04.870729+00:00 heroku[web.1]: State changed from starting to up 2018-05-30T12:03:07.989892+00:00 app[web.1]: [deprecated] Mozilla certificates have been deprecated and will be removed in future releases. Using 'defaults' instead. 2018-05-30T12:03:07.989934+00:00 app[web.1]: oauthJson line 76 is JSON(node: Node.Node.object(["token_type": Node.Node.string("Bearer"), "access_token": Node.Node.string("ya29.c.ElvLBagSETr9DiPFjitMs4XtijYEdFR1OT4jDQx4WpwKkXX1ghZZQ3fMoaK7_07MlMOh8STSFeP4Xr4MpSeXg9_Sjk1xjBC19sLEWFoUhPMMyYKNem4oTOL-qXK_"), "expires_in": Node.Node.number(3600)])) 2018-05-30T12:03:07.989936+00:00 app[web.1]: [deprecated] Mozilla certificates have been deprecated and will be removed in future releases. Using 'defaults' instead. 2018-05-30T12:03:08.147558+00:00 app[web.1]: [deprecated] Mozilla certificates have been deprecated and will be removed in future releases. Using 'defaults' instead. 2018-05-30T12:03:08.887563+00:00 heroku[router]: at=info method=POST path="/cancelbooking/bycleaner" host=secure-garden-28988.herokuapp.com request_id=c4acdc48-ac25-42ed-9092-97bbc8a05b6c fwd="90.219.212.108" dyno=web.1 connect=2ms service=1266ms status=200 bytes=155 protocol=https 2018-05-30T12:39:40.718231+00:00 heroku[web.1]: Idling 2018-05-30T12:39:40.718872+00:00 heroku[web.1]: State changed from up to down 2018-05-30T12:39:42.031561+00:00 heroku[web.1]: Stopping all processes with SIGTERM 2018-05-30T12:39:42.052902+00:00 app[web.1]: response data is on line 135 Optional(Vapor.Content) and response.json is on line 135 Optional(JSON.JSON(node: Node.Node.object(["LaundryWash": Node.Node.string("false"), "CancelledBy": Node.Node.object(["1527631309": Node.Node.object(["TimeStampDateAndTime": Node.Node.string("1527694200"), "ChangesMadeBy": Node.Node.string("Cleaner"), "usersUID": Node.Node.string("YL6jLbKq04XF2V0AX9PapH2jv8n2"), "CancelledByCleaner": Node.Node.string("6XkGK4amQ3dmp5DcIEWLWCAChzm1"), "ReasonDebtToCleaner": Node.Node.string("Booking cancelled by cleaner 17 hours, 28 minutes, 12 seconds before booking start time"), "DateAndTime": Node.Node.string("Wed, 30 May 2018 16:30"), "BookingStateTimeStamp": Node.Node.string("1527631309"), "CleanerUID": Node.Node.string("6XkGK4amQ3dmp5DcIEWLWCAChzm1"), "Claimed": Node.Node.string("false"), "TimeStampBookingCancelledByCleaner": Node.Node.string("1527631309"), "AmountDebtToCleaner": Node.Node.string("0"), "BookingNumber": Node.Node.string("236191463"), "CostToCanc...</code>
问题分析
从日志和错误信息里抠出几个关键线索:
- Heroku明确提示咱用的是过时的运行栈,这大概率是根源——旧栈的系统库、依赖版本和本地差异太大,容易导致兼容性问题。
- 本地正常线上崩,十有八九是生产模式的配置差异。日志里写了
Production mode enabled, disabling informational logs,生产模式默认把调试日志关了,导致咱看不到服务器内部到底为啥抛错,只能拿到Heroku的兜底错误页。 - 客户端收到的是HTML错误页,不是咱自己返回的JSON,所以Alamofire解析直接炸了——这说明服务器处理请求时肯定抛出了未捕获的异常,才触发了Heroku的默认错误页。
一步步解决的方案
1. 先升级Heroku栈(最优先)
旧栈的坑太多,先切换到稳定版本:
# 切换到heroku-18栈(对Vapor 1.5兼容性极佳) heroku stack:set heroku-18 # 提交修改重新部署 git add . git commit -m "升级Heroku栈到heroku-18" git push heroku master
2. 打开生产模式的详细日志
生产模式默认关了日志,咱根本看不到内部错误。修改Procfile里的启动命令,加上日志参数:
web: App --env=production --workdir=./ --config:servers.default.port=$PORT --log=verbose
重新部署后,再用heroku logs就能看到详细的错误信息,定位问题就容易多了。
3. 检查JSON响应的编码和结构
本地和线上可能存在字符编码或者JSON结构的差异:
- 手动指定响应的Content-Type为UTF-8,避免编码问题:
在路由的响应代码里加上:return JSON(node: yourNode).makeResponse().withHeaders(["Content-Type": "application/json; charset=utf-8"]) - 检查你的Node结构里有没有特殊字符,或者未处理的可选值——生产环境对这些细节比本地严格得多。
4. 处理Heroku的休眠问题
日志里显示dyno进入了Idle状态,闲置一段时间后Heroku会把服务器休眠,第一次请求唤醒的时候可能会超时或者初始化失败。解决办法:
- 去Heroku控制台开启自动唤醒,或者用工具定期ping你的服务器(比如UptimeRobot)
- 检查应用在唤醒时的初始化逻辑,比如数据库连接、第三方服务的初始化有没有问题。
5. 确认Swift版本匹配
虽然Heroku说用的是Swift 3.1,但得确保应用确实是用这个版本编译的。在Package.swift里明确指定:
let package = Package( name: "YourApp", swiftVersion: "3.1", // 其他配置... )
重新编译部署,确保Heroku用对了Swift版本。
总结
最有可能的原因就是旧Heroku栈导致的环境不兼容,先升级栈,再开详细日志找具体错误。如果还不行,就检查JSON响应的编码和生产模式下的初始化逻辑,应该就能搞定了。
内容的提问来源于stack exchange,提问作者bibscy

