如何在Kotlin Ktor中传递ISBN参数调用Google Books API
问题描述
我想用Google Books API实现ISBN书籍搜索,技术栈为Kotlin+Ktor框架,前端采用HTML/CSS实现。当前遇到的问题是:输入ISBN后提交表单无任何反应,页面不跳转,无法传递ISBN参数调用API。
相关代码如下:
Routing.kt
fun Application.configureRouting() { routing { static("/static") { resources("files") } get("/") { call.respondRedirect("books") } route("search") { get { call.respond(FreeMarkerContent("search.ftl", model = null)) } get("field") { val formParameters = call.receiveParameters() val isbn = formParameters.getOrFail("isbn").toLong() val client = HttpClient(CIO) val response: HttpResponse = client.get("https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn") println(response.status) val stringBody: String = response.body() println(stringBody) client.close() call.respondRedirect("/search") } } } }
注:直接在URL中硬编码ISBN时能正常获取API响应,说明API调用逻辑本身无问题。
search.ftl
<#import "_layout.ftl" as layout /> <@layout.header> <div> <div class="text-center"> <h1 class="display-4">Search</h1> </div> <div class="container"> <div class="row"> <div class="form-group has-search"> <span class="fa fa-search form-control-feedback"></span> <form action="/search/field" method="get"> <input type="text" class="form-control" name="isbn"> </form> </div> </div> </@layout.header>
解决方法
1. 修复前端表单提交触发问题
当前表单缺少提交按钮,用户输入ISBN后无法触发提交动作,导致请求未发送至后端。需添加提交按钮:
<#import "_layout.ftl" as layout /> <@layout.header> <div> <div class="text-center"> <h1 class="display-4">Search</h1> </div> <div class="container"> <div class="row"> <div class="form-group has-search"> <span class="fa fa-search form-control-feedback"></span> <form action="/search/field" method="get"> <input type="text" class="form-control" name="isbn"> <!-- 添加提交按钮 --> <button type="submit" class="btn btn-primary mt-2">搜索</button> </form> </div> </div> </@layout.header>
2. 修正后端参数获取方式
GET请求的参数存于URL的query字符串中,而非请求体的表单参数,不能使用call.receiveParameters(),需改用call.request.queryParameters获取:
get("field") { // 从query参数中获取ISBN,无需转Long(ISBN可能含分隔符/前缀) val isbn = call.request.queryParameters.getOrFail("isbn") val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn" val client = HttpClient(CIO) try { val response = client.get(apiUrl) val bookJson = response.body<String>() // 直接返回JSON给前端,或传入模板渲染结果页 call.respondText(bookJson, ContentType.Application.Json) } catch (e: Exception) { // 处理异常场景(如ISBN无效、API请求失败) call.respondText("搜索失败:${e.message}", ContentType.Text.Plain, HttpStatusCode.BadRequest) } finally { client.close() } }
3. 优化HttpClient复用(推荐)
每次请求新建HttpClient会造成资源浪费,建议在Application级别初始化全局实例:
fun Application.configureRouting() { // 全局初始化HttpClient val httpClient = HttpClient(CIO) { install(HttpTimeout) { requestTimeoutMillis = 10000 } } routing { static("/static") { resources("files") } get("/") { call.respondRedirect("books") } route("search") { get { call.respond(FreeMarkerContent("search.ftl", model = null)) } get("field") { val isbn = call.request.queryParameters.getOrFail("isbn") val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn" try { val response = httpClient.get(apiUrl) val bookJson = response.body<String>() call.respondText(bookJson, ContentType.Application.Json) } catch (e: Exception) { call.respondText("搜索失败:${e.message}", ContentType.Text.Plain, HttpStatusCode.BadRequest) } } } } // 应用停止时关闭HttpClient environment.monitor.subscribe(ApplicationStopped) { httpClient.close() } }
4. 移除不必要的重定向
原代码中的call.respondRedirect("/search")会丢失API返回数据,若需展示搜索结果,可将数据传入模板渲染:
get("field") { val isbn = call.request.queryParameters.getOrFail("isbn") val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn" try { val response = httpClient.get(apiUrl) val bookJson = response.body<String>() // 传入结果模板渲染 call.respond(FreeMarkerContent("search_result.ftl", mapOf("bookData" to bookJson), null)) } catch (e: Exception) { // 错误信息传回搜索页展示 call.respond(FreeMarkerContent("search.ftl", mapOf("error" to "搜索失败:${e.message}"), null)) } }
内容的提问来源于stack exchange,提问作者nicowi
相关产品推荐
相关产品推荐

