BaseRequest.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /*
  2. * Copyright (C) 2018 Drake, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. @file:Suppress("unused", "MemberVisibilityCanBePrivate", "NAME_SHADOWING", "RedundantSetter")
  17. package com.drake.net.request
  18. import com.drake.net.NetConfig
  19. import com.drake.net.convert.NetConverter
  20. import com.drake.net.exception.URLParseException
  21. import com.drake.net.interfaces.NetCallback
  22. import com.drake.net.interfaces.ProgressListener
  23. import com.drake.net.okhttp.toNetOkhttp
  24. import com.drake.net.response.convert
  25. import com.drake.net.tag.NetTag
  26. import com.drake.net.utils.runMain
  27. import okhttp3.*
  28. import okhttp3.HttpUrl.Companion.toHttpUrl
  29. import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
  30. import java.io.File
  31. import java.io.IOException
  32. import java.net.URL
  33. import java.util.concurrent.CancellationException
  34. import kotlin.reflect.typeOf
  35. abstract class BaseRequest {
  36. /** 请求的Url构造器 */
  37. open var httpUrl: HttpUrl.Builder = HttpUrl.Builder()
  38. /** 当前请求的数据转换器 */
  39. open var converter: NetConverter = NetConfig.converter
  40. /** 请求的方法 */
  41. open var method = Method.GET
  42. //<editor-fold desc="OkHttpClient" >
  43. /** 请求对象构造器 */
  44. open var okHttpRequest: Request.Builder = Request.Builder()
  45. /** 请求客户端 */
  46. open var okHttpClient = NetConfig.okHttpClient
  47. set(value) {
  48. field = value.toNetOkhttp()
  49. }
  50. /**
  51. * 修改当前Request的OkHttpClient配置, 不会影响全局默认的OkHttpClient
  52. */
  53. fun setClient(block: OkHttpClient.Builder.() -> Unit) {
  54. okHttpClient = okHttpClient.newBuilder().apply(block).toNetOkhttp().build()
  55. }
  56. //</editor-fold>
  57. //<editor-fold desc="ID">
  58. /**
  59. * 唯一的Id
  60. */
  61. fun setId(id: Any?) {
  62. okHttpRequest.id = id
  63. }
  64. /**
  65. * 分组
  66. */
  67. fun setGroup(group: Any?) {
  68. okHttpRequest.group = group
  69. }
  70. //</editor-fold>
  71. //<editor-fold desc="URL">
  72. /**
  73. * 设置一个Url字符串, 其参数不会和你初始化时设置的主域名[NetConfig.host]进行拼接
  74. * 一般情况下我建议使用更为聪明的[setPath]
  75. */
  76. open fun setUrl(url: String) {
  77. try {
  78. httpUrl = url.toHttpUrl().newBuilder()
  79. } catch (e: Exception) {
  80. throw URLParseException(url, e)
  81. }
  82. }
  83. /**
  84. * 设置Url
  85. */
  86. open fun setUrl(url: HttpUrl) {
  87. httpUrl = url.newBuilder()
  88. }
  89. /**
  90. * 设置Url
  91. */
  92. open fun setUrl(url: URL) {
  93. setUrl(url.toString())
  94. }
  95. /**
  96. * 解析配置Path, 支持识别query参数和绝对路径
  97. * @param path 如果其不包含http/https则会自动拼接[NetConfig.host]
  98. */
  99. fun setPath(path: String?) {
  100. val url = path?.toHttpUrlOrNull()
  101. if (url == null) {
  102. try {
  103. httpUrl = (NetConfig.host + path).toHttpUrl().newBuilder()
  104. } catch (e: Throwable) {
  105. throw URLParseException(NetConfig.host + path, e)
  106. }
  107. } else {
  108. this.httpUrl = url.newBuilder()
  109. }
  110. }
  111. /**
  112. * 设置Url上的Query参数
  113. */
  114. fun setQuery(name: String, value: String?, encoded: Boolean = false) {
  115. if (encoded) {
  116. httpUrl.setEncodedQueryParameter(name, value)
  117. } else {
  118. httpUrl.setQueryParameter(name, value)
  119. }
  120. }
  121. /**
  122. * 设置Url上的Query参数
  123. */
  124. fun setQuery(name: String, value: Number?) {
  125. setQuery(name, value?.toString() ?: return)
  126. }
  127. /**
  128. * 设置Url上的Query参数
  129. */
  130. fun setQuery(name: String, value: Boolean?) {
  131. setQuery(name, value?.toString() ?: return)
  132. }
  133. //</editor-fold>
  134. //<editor-fold desc="Param">
  135. /**
  136. * 基础类型表单参数
  137. *
  138. * 如果当前请求为Url请求则为Query参数
  139. * 如果当前请求为表单请求则为表单参数
  140. * 如果当前为Multipart包含流/文件的请求则为multipart参数
  141. */
  142. abstract fun param(name: String, value: String?)
  143. /**
  144. * 基础类型表单参数
  145. *
  146. * 如果当前请求为Url请求则为Query参数
  147. * 如果当前请求为表单请求则为表单参数
  148. * 如果当前为Multipart包含流/文件的请求则为multipart参数
  149. *
  150. * @param encoded 对应OkHttp参数函数中的encoded表示当前字段参数已经编码过. 不会再被自动编码
  151. */
  152. abstract fun param(name: String, value: String?, encoded: Boolean)
  153. /**
  154. * 基础类型表单参数
  155. *
  156. * 如果当前请求为Url请求则为Query参数
  157. * 如果当前请求为表单请求则为表单参数
  158. * 如果当前为Multipart包含流/文件的请求则为multipart参数
  159. */
  160. abstract fun param(name: String, value: Number?)
  161. /**
  162. * 基础类型表单参数
  163. *
  164. * 如果当前请求为Url请求则为Query参数
  165. * 如果当前请求为表单请求则为表单参数
  166. * 如果当前为Multipart包含流/文件的请求则为multipart参数
  167. */
  168. abstract fun param(name: String, value: Boolean?)
  169. //</editor-fold>
  170. //<editor-fold desc="Extra">
  171. /**
  172. * 添加标签
  173. * 使用`Request.tag(name)`得到指定标签
  174. *
  175. * @param name 标签名称
  176. * @param tag 标签
  177. */
  178. fun setExtra(name: String, tag: Any?) {
  179. okHttpRequest.setExtra(name, tag)
  180. }
  181. //</editor-fold>
  182. //<editor-fold desc="Tag">
  183. /**
  184. * 使用Any::class作为键名添加标签
  185. * 使用Request.tag()返回标签
  186. */
  187. fun tag(tag: Any?) {
  188. okHttpRequest.tag(tag)
  189. }
  190. /**
  191. * 使用[type]作为键名添加标签
  192. * 使用Request.label<T>()或者Request.tag(type)返回标签
  193. */
  194. fun <T> tag(type: Class<in T>, tag: T?) {
  195. okHttpRequest.tag(type, tag)
  196. }
  197. /**
  198. * 使用[T]作为键名添加标签
  199. * 使用Request.label<T>()或者Request.tag(type)返回标签
  200. */
  201. inline fun <reified T> tagOf(tag: T?) {
  202. okHttpRequest.tagOf(tag)
  203. }
  204. //</editor-fold>
  205. //<editor-fold desc="Header">
  206. /**
  207. * 添加请求头
  208. * 如果已存在相同`name`的请求头会添加而不会覆盖, 因为请求头本身存在多个值
  209. */
  210. fun addHeader(name: String, value: String) {
  211. okHttpRequest.addHeader(name, value)
  212. }
  213. /**
  214. * 设置请求头, 会覆盖请求头而不像[addHeader]是添加
  215. */
  216. fun setHeader(name: String, value: String) {
  217. okHttpRequest.header(name, value)
  218. }
  219. /**
  220. * 删除请求头
  221. */
  222. fun removeHeader(name: String) {
  223. okHttpRequest.removeHeader(name)
  224. }
  225. /**
  226. * 批量设置请求头
  227. */
  228. fun setHeaders(headers: Headers) {
  229. okHttpRequest.headers(headers)
  230. }
  231. //</editor-fold>
  232. //<editor-fold desc="Cache">
  233. /**
  234. * 设置请求头的缓存控制
  235. */
  236. fun setCacheControl(cacheControl: CacheControl) {
  237. okHttpRequest.cacheControl(cacheControl)
  238. }
  239. //</editor-fold>
  240. //<editor-fold desc="Download">
  241. /**
  242. * 下载文件名
  243. * 如果[setDownloadDir]函数使用完整路径(包含文件名的参数)作为参数则将无视本函数设置
  244. * 如果不调用本函数则默认是读取服务器返回的文件名
  245. * @see setDownloadFileNameDecode
  246. * @see setDownloadFileNameConflict
  247. * @see setDownloadDir
  248. */
  249. fun setDownloadFileName(name: String?) {
  250. okHttpRequest.tagOf(NetTag.DownloadFileName(name))
  251. }
  252. /**
  253. * 下载保存的目录, 也支持包含文件名称的完整路径, 如果使用完整路径则无视setDownloadFileName设置
  254. */
  255. fun setDownloadDir(name: String?) {
  256. okHttpRequest.tagOf(NetTag.DownloadFileDir(name))
  257. }
  258. /**
  259. * 下载保存的目录, 也支持包含文件名称的完整路径, 如果使用完整路径则无视setDownloadFileName设置
  260. */
  261. fun setDownloadDir(name: File?) {
  262. okHttpRequest.tagOf(NetTag.DownloadFileDir(name))
  263. }
  264. /**
  265. * 如果服务器返回 "Content-MD5"响应头和制定路径已经存在的文件MD5相同是否直接返回File
  266. */
  267. fun setDownloadMd5Verify(enabled: Boolean = true) {
  268. okHttpRequest.tagOf(NetTag.DownloadFileMD5Verify(enabled))
  269. }
  270. /**
  271. * 假设下载文件路径已存在同名文件是否重命名, 例如`file_name(1).apk`
  272. */
  273. fun setDownloadFileNameConflict(enabled: Boolean = true) {
  274. okHttpRequest.tagOf(NetTag.DownloadFileConflictRename(enabled))
  275. }
  276. /**
  277. * 文件名称是否使用URL解码
  278. * 例如下载的文件名如果是中文, 服务器传输给你的会是被URL编码的字符串. 你使用URL解码后才是可读的中文名称
  279. */
  280. fun setDownloadFileNameDecode(enabled: Boolean = true) {
  281. okHttpRequest.tagOf(NetTag.DownloadFileNameDecode(enabled))
  282. }
  283. /**
  284. * 下载是否使用临时文件
  285. * 避免下载失败后覆盖同名文件或者无法判别是否已下载完整, 仅在下载完整以后才会显示为原有文件名
  286. * 临时文件命名规则: 文件名 + .net-download
  287. * 下载文件名: install.apk, 临时文件名: install.apk.net-download
  288. */
  289. fun setDownloadTempFile(enabled: Boolean = true) {
  290. okHttpRequest.tagOf(NetTag.DownloadTempFile(enabled))
  291. }
  292. /**
  293. * 下载监听器
  294. */
  295. fun addDownloadListener(progressListener: ProgressListener) {
  296. okHttpRequest.downloadListeners().add(progressListener)
  297. }
  298. //</editor-fold>
  299. /**
  300. * 是否启用日志记录器
  301. */
  302. fun setLogRecord(enabled: Boolean) {
  303. okHttpRequest.logRecord = enabled
  304. }
  305. /**
  306. * 为请求附着针对Kotlin的Type信息
  307. */
  308. @OptIn(ExperimentalStdlibApi::class)
  309. inline fun <reified T> setKType() {
  310. okHttpRequest.kType = typeOf<T>()
  311. }
  312. /**
  313. * 构建请求对象Request
  314. */
  315. open fun buildRequest(): Request {
  316. return okHttpRequest.method(method.name, null)
  317. .url(httpUrl.build())
  318. .setConverter(converter)
  319. .build()
  320. }
  321. //<editor-fold desc="SyncRequest">
  322. /**
  323. * 执行同步请求
  324. */
  325. @OptIn(ExperimentalStdlibApi::class)
  326. inline fun <reified R> execute(): R {
  327. NetConfig.requestInterceptor?.interceptor(this)
  328. setKType<R>()
  329. val request = buildRequest()
  330. val newCall = okHttpClient.newCall(request)
  331. return newCall.execute().convert(converter)
  332. }
  333. /**
  334. * 执行同步请求
  335. * @return 一个包含请求成功和错误的Result
  336. */
  337. inline fun <reified R> toResult(): Result<R> {
  338. NetConfig.requestInterceptor?.interceptor(this)
  339. setKType<R>()
  340. val request = buildRequest()
  341. val newCall = okHttpClient.newCall(request)
  342. return try {
  343. val value = newCall.execute().convert<R>(converter)
  344. Result.success(value)
  345. } catch (e: Exception) {
  346. Result.failure(e)
  347. }
  348. }
  349. //</editor-fold>
  350. //<editor-fold desc="EnqueueRequest">
  351. /**
  352. * 队列请求. 支持OkHttp的Callback函数组件
  353. */
  354. fun enqueue(block: Callback): Call {
  355. NetConfig.requestInterceptor?.interceptor(this)
  356. val request = buildRequest()
  357. val newCall = okHttpClient.newCall(request)
  358. if (block is NetCallback<*>) {
  359. block.request = request
  360. block.onStart(request)
  361. }
  362. newCall.enqueue(block)
  363. return newCall
  364. }
  365. /**
  366. * 队列请求. 支持Result作为请求结果
  367. */
  368. inline fun <reified R> onResult(crossinline block: Result<R>.() -> Unit): Call {
  369. NetConfig.requestInterceptor?.interceptor(this)
  370. val newCall = okHttpClient.newCall(buildRequest())
  371. setKType<R>()
  372. newCall.enqueue(object : Callback {
  373. override fun onFailure(call: Call, e: IOException) {
  374. val message = e.cause?.message
  375. val exception = if (message == "Socket closed") {
  376. CancellationException(message)
  377. } else e
  378. runMain { block(Result.failure(exception)) }
  379. }
  380. override fun onResponse(call: Call, response: Response) {
  381. val success = try {
  382. response.convert<R>(converter)
  383. } catch (e: IOException) {
  384. onFailure(call, e)
  385. return
  386. }
  387. runMain { block(Result.success(success)) }
  388. }
  389. })
  390. return newCall
  391. }
  392. //</editor-fold>
  393. }