@@ -2,11 +2,13 @@ package name.valery1707.problem
2
2
3
3
import java.net.URI
4
4
import java.net.http.HttpClient
5
+ import java.net.http.HttpHeaders
5
6
import java.net.http.HttpRequest
6
7
import java.net.http.HttpResponse
7
8
import java.nio.file.Path
8
9
import java.time.Duration
9
10
import java.time.Instant
11
+ import java.time.format.DateTimeFormatter
10
12
import java.time.temporal.ChronoField.NANO_OF_SECOND
11
13
import kotlin.io.path.ExperimentalPathApi
12
14
import kotlin.io.path.PathWalkOption
@@ -115,19 +117,7 @@ class LinkChecker(private val root: Path) {
115
117
// Rate limiting: wait and retry
116
118
in HTTP_RATE_LIMIT -> {
117
119
val now = Instant .now()
118
- val await = response.headers()
119
-
120
- // todo Extract to method
121
- // https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#checking-your-rate-limit-status
122
- .map()[" x-ratelimit-reset" ]
123
- ?.asSequence()
124
- ?.map(String ::toLong)?.map(Instant ::ofEpochSecond)
125
- ?.map { Duration .between(now.with (NANO_OF_SECOND , 0 ), it) }
126
- ?.map(Duration ::toMillis)
127
- ?.filter { it >= 0 }
128
- ?.firstOrNull()
129
-
130
- ? : 500
120
+ val await = response.headers().rateLimitAwait(now) ? : 500
131
121
132
122
logger.debug(" Await: $await ms" )
133
123
Thread .sleep(await)
@@ -143,6 +133,40 @@ class LinkChecker(private val root: Path) {
143
133
}
144
134
145
135
private val HTTP_REDIRECT = setOf (301 , 302 , 307 , 308 )
146
- private val HTTP_RATE_LIMIT = setOf (403 )
136
+ private val HTTP_RATE_LIMIT = setOf (403 , 429 )
137
+
138
+ private fun HttpHeaders.rateLimitAwait (now : Instant ): Long? {
139
+ val map = map()
140
+ return HTTP_RATE_LIMIT_EXTRACTORS
141
+ .flatMap { map[it.key]?.asSequence()?.map { v -> it.value(v.trim(), now) } ? : emptySequence() }
142
+ .filterNotNull()
143
+ .firstOrNull { it >= 0 }
144
+ }
145
+
146
+ private val HTTP_RATE_LIMIT_EXTRACTORS : Map <String , (String , Instant ) - > Long? > = mapOf (
147
+ // https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#checking-your-rate-limit-status
148
+ " x-ratelimit-reset" to { value, now ->
149
+ value
150
+ .toLong()
151
+ .let (Instant ::ofEpochSecond)
152
+ .let { Duration .between(now.with (NANO_OF_SECOND , 0 ), it) }
153
+ .let (Duration ::toMillis)
154
+ },
155
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
156
+ " Retry-After" to { value, now ->
157
+ if (value.isDigit()) value.toLong()
158
+ else HTTP_DATE_FORMAT
159
+ .parse(value, Instant ::from)
160
+ .let { Duration .between(now.with (NANO_OF_SECOND , 0 ), it) }
161
+ .let (Duration ::toMillis)
162
+ },
163
+ )
164
+
165
+ /* *
166
+ * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date">Specification</a>
167
+ */
168
+ internal val HTTP_DATE_FORMAT = DateTimeFormatter .RFC_1123_DATE_TIME
169
+
170
+ private fun String.isDigit (): Boolean = this .all { it.isDigit() }
147
171
}
148
172
}
0 commit comments