perf: use undici instead of axios

This commit is contained in:
Starcea 2024-11-02 14:00:41 +09:00
commit 5a15d50cfc
No known key found for this signature in database
GPG key ID: B7A77E32374911E1
5 changed files with 59 additions and 97 deletions

View file

@ -15,7 +15,13 @@
"prepack": "pnpm build",
"test": "node -r ts-node/register --test tests/*.test.ts"
},
"keywords": ["comcigan", "parser", "typescript", "school", "korean"],
"keywords": [
"comcigan",
"parser",
"typescript",
"school",
"korean"
],
"author": "Starcea <stardev.uwu@gmail.com>",
"license": "LGPL-3.0-or-later",
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a",
@ -28,10 +34,12 @@
"typescript": "^5.5.2"
},
"dependencies": {
"axios": "^1.7.2",
"iconv-lite": "^0.6.3"
"iconv-lite": "^0.6.3",
"undici": "^6.20.1"
},
"release": {
"branches": ["release"]
"branches": [
"release"
]
}
}

82
pnpm-lock.yaml generated
View file

@ -8,12 +8,12 @@ importers:
.:
dependencies:
axios:
specifier: ^1.7.2
version: 1.7.2
iconv-lite:
specifier: ^0.6.3
version: 0.6.3
undici:
specifier: ^6.20.1
version: 6.20.1
devDependencies:
'@biomejs/biome':
specifier: ^1.8.3
@ -327,16 +327,10 @@ packages:
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
engines: {node: '>= 0.4'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
axios@1.7.2:
resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -410,10 +404,6 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
compare-func@2.0.0:
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
@ -498,10 +488,6 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@ -619,15 +605,6 @@ packages:
resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==}
engines: {node: '>=18'}
follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -635,10 +612,6 @@ packages:
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
engines: {node: '>=14'}
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
from2@2.3.0:
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
@ -1012,14 +985,6 @@ packages:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime@4.0.3:
resolution: {integrity: sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==}
engines: {node: '>=16'}
@ -1279,9 +1244,6 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -1579,6 +1541,10 @@ packages:
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici@6.20.1:
resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==}
engines: {node: '>=18.17'}
unicode-emoji-modifier-base@1.0.0:
resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
engines: {node: '>=4'}
@ -1983,20 +1949,10 @@ snapshots:
is-array-buffer: 3.0.4
is-shared-array-buffer: 1.0.3
asynckit@0.4.0: {}
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.0.0
axios@1.7.2:
dependencies:
follow-redirects: 1.15.6
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {}
before-after-hook@3.0.2: {}
@ -2079,10 +2035,6 @@ snapshots:
color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
compare-func@2.0.0:
dependencies:
array-ify: 1.0.0
@ -2172,8 +2124,6 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
delayed-stream@1.0.0: {}
diff@4.0.2: {}
dir-glob@3.0.1:
@ -2348,8 +2298,6 @@ snapshots:
semver-regex: 4.0.5
super-regex: 1.0.0
follow-redirects@1.15.6: {}
for-each@0.3.3:
dependencies:
is-callable: 1.2.7
@ -2359,12 +2307,6 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
form-data@4.0.0:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
from2@2.3.0:
dependencies:
inherits: 2.0.4
@ -2724,12 +2666,6 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime@4.0.3: {}
mimic-fn@4.0.0: {}
@ -2884,8 +2820,6 @@ snapshots:
proto-list@1.2.4: {}
proxy-from-env@1.1.0: {}
queue-microtask@1.2.3: {}
rc@1.2.8:
@ -3260,6 +3194,8 @@ snapshots:
undici-types@5.26.5: {}
undici@6.20.1: {}
unicode-emoji-modifier-base@1.0.0: {}
unicorn-magic@0.1.0: {}

View file

@ -1,6 +1,6 @@
import axios from 'axios'
import { BASE_URL, USER_AGENT } from './constants'
import DataManager from './data'
import HTTP from './http'
import School from './models/School'
import type { Timetable } from './models/Timetable'
import { mergeMap } from './utils/array'
@ -8,24 +8,22 @@ import { encodeBase64, encodeEUCKR } from './utils/encode'
import { parseResponse } from './utils/parse'
export default class Comcigan {
private readonly rest = axios.create({
private readonly http = new HTTP({
baseURL: BASE_URL,
headers: {
'User-Agent': USER_AGENT,
},
headers: { 'User-Agent': USER_AGENT },
})
private readonly dataManager = new DataManager(this.rest)
private readonly dataManager = new DataManager(this.http)
/** 학교를 검색합니다. */
async searchSchools(schoolName: string): Promise<School[]> {
const { mainRoute, searchRoute } = await this.dataManager.getData()
const res = await this.rest.get(
`${mainRoute}?${searchRoute}l${encodeEUCKR(schoolName)}`,
)
const res = await this.http
.get(`${mainRoute}?${searchRoute}l${encodeEUCKR(schoolName)}`)
.then((res) => res.body.text())
const { 학교검색: data } = parseResponse<{
: [number, string, string, number][]
}>(res.data)
}>(res)
return data.map(
([regionCode, regionName, schoolName, schoolCode]) =>
@ -48,10 +46,12 @@ export default class Comcigan {
dayCode,
subjectCode,
} = await this.dataManager.getData()
const res = await this.rest.get(
const res = await this.http
.get(
`${mainRoute}_T?${encodeBase64(`${timetableRoute}_${schoolCode}_0_1`)}`,
)
const data = parseResponse(res.data)
.then((res) => res.body.text())
const data = parseResponse(res)
const teachers = data[`자료${teacherCode}`] as string[]
const teachersLen = Math.floor(Math.log10(teachers.length - 1)) + 1

View file

@ -1,6 +1,6 @@
import type { AxiosInstance } from 'axios'
import { decode } from 'iconv-lite'
import { RegExes } from './constants'
import type HTTP from './http'
interface Data {
mainRoute: string
@ -18,13 +18,11 @@ export default class DataManager {
private _lastFetchDate = 0
constructor(private readonly rest: AxiosInstance) {}
constructor(private readonly http: HTTP) {}
private async fetchData(): Promise<Data> {
const res = await this.rest.get('/st', {
responseType: 'arraybuffer',
})
const data = decode(Buffer.from(res.data), 'euc-kr')
const res = await this.http.get('/st').then((res) => res.body.arrayBuffer())
const data = decode(Buffer.from(res), 'euc-kr')
const main = RegExes.MainRoute.exec(data)
if (!main) throw new Error('Failed to fetch main route')

20
src/http.ts Normal file
View file

@ -0,0 +1,20 @@
import { request } from 'undici'
export default class HTTP {
private readonly baseURL: string
private readonly headers: Record<string, string>
constructor({
baseURL,
headers,
}: { baseURL: string; headers: Record<string, string> }) {
this.baseURL = baseURL
this.headers = headers
}
async get(url: string) {
return request(`${new URL(url, this.baseURL)}`, {
headers: this.headers,
})
}
}