@@ -4,6 +4,10 @@ import { URL } from "url";
4
4
import { Readable } from "stream" ;
5
5
import { getOrCreateSSLCerts } from "../utils/ssl" ;
6
6
import { formatHost } from "../utils/network" ;
7
+ import NodeCache from "node-cache" ;
8
+
9
+ const hasRootHashCache = new NodeCache ( { stdTTL : 86400 } ) ;
10
+ const wellKnownCache = new NodeCache ( { stdTTL : 86400 } ) ;
7
11
8
12
export class ContentServer {
9
13
private ipAddress : string ;
@@ -30,7 +34,9 @@ export class ContentServer {
30
34
challengeHex ?: string
31
35
) : Promise < string > {
32
36
// Construct the base URL
33
- let url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /chia.${ this . storeId } .${ rootHash } /${ key } ` ;
37
+ let url = `https://${ formatHost ( this . ipAddress ) } :${
38
+ ContentServer . port
39
+ } /chia.${ this . storeId } .${ rootHash } /${ key } `;
34
40
35
41
// If a challenge is provided, append it as a query parameter
36
42
if ( challengeHex ) {
@@ -40,13 +46,12 @@ export class ContentServer {
40
46
return this . fetchWithRetries ( url ) ;
41
47
}
42
48
43
- // New method to get only the first chunk of the content
44
- public async getKeyChunk (
45
- key : string ,
46
- rootHash : string ,
47
- ) : Promise < Buffer > {
49
+ // New method to get only the first chunk of the content
50
+ public async getKeyChunk ( key : string , rootHash : string ) : Promise < Buffer > {
48
51
// Construct the base URL
49
- let url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /chia.${ this . storeId } .${ rootHash } /${ key } ` ;
52
+ let url = `https://${ formatHost ( this . ipAddress ) } :${
53
+ ContentServer . port
54
+ } /chia.${ this . storeId } .${ rootHash } /${ key } `;
50
55
return this . fetchFirstChunk ( url ) ;
51
56
}
52
57
@@ -65,15 +70,44 @@ export class ContentServer {
65
70
}
66
71
}
67
72
68
- // Method to get the .well-known information
73
+ /**
74
+ * Fetches and caches the .well-known information for the store's IP address.
75
+ *
76
+ * @returns A promise that resolves to the .well-known JSON data.
77
+ */
69
78
public async getWellKnown ( ) : Promise < any > {
70
- const url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /.well-known` ;
71
- return this . fetchJson ( url ) ;
79
+ // Construct the cache key based on ipAddress
80
+ const cacheKey = `${ this . ipAddress } -wellknown` ;
81
+
82
+ // Check if the result is already cached
83
+ const cachedResult = wellKnownCache . get < any > ( cacheKey ) ;
84
+ if ( cachedResult !== undefined ) {
85
+ return cachedResult ;
86
+ }
87
+
88
+ // If not cached, proceed to fetch the .well-known information
89
+ const url = `https://${ formatHost ( this . ipAddress ) } :${
90
+ ContentServer . port
91
+ } /.well-known`;
92
+
93
+ try {
94
+ const data = await this . fetchJson ( url ) ;
95
+ wellKnownCache . set ( cacheKey , data ) ;
96
+ return data ;
97
+ } catch ( error : any ) {
98
+ console . error (
99
+ `Error fetching .well-known information for ${ this . ipAddress } :` ,
100
+ error . message
101
+ ) ;
102
+ throw error ; // Propagate the error after logging
103
+ }
72
104
}
73
105
74
106
// Method to get the list of known stores
75
107
public async getKnownStores ( ) : Promise < any > {
76
- const url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /.well-known/stores` ;
108
+ const url = `https://${ formatHost ( this . ipAddress ) } :${
109
+ ContentServer . port
110
+ } /.well-known/stores`;
77
111
return this . fetchJson ( url ) ;
78
112
}
79
113
@@ -85,53 +119,103 @@ export class ContentServer {
85
119
86
120
// Method to get the index of keys in a store
87
121
public async getKeysIndex ( rootHash ?: string ) : Promise < any > {
88
- let udi = `chia.${ this . storeId } ` ;
122
+ try {
123
+ let udi = `chia.${ this . storeId } ` ;
89
124
90
- if ( rootHash ) {
91
- udi += `.${ rootHash } ` ;
92
- }
125
+ if ( rootHash ) {
126
+ udi += `.${ rootHash } ` ;
127
+ }
93
128
94
- const url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /${ udi } ` ;
95
- return this . fetchJson ( url ) ;
129
+ const url = `https://${ formatHost ( this . ipAddress ) } :${
130
+ ContentServer . port
131
+ } /${ udi } `;
132
+ return this . fetchJson ( url ) ;
133
+ } catch ( error : any ) {
134
+ if ( rootHash ) {
135
+ hasRootHashCache . del ( `${ this . storeId } -${ rootHash } ` ) ;
136
+ }
137
+ throw error ;
138
+ }
96
139
}
97
140
98
141
// Method to check if a specific key exists (HEAD request)
99
142
public async headKey (
100
143
key : string ,
101
144
rootHash ?: string
102
145
) : Promise < { success : boolean ; headers ?: http . IncomingHttpHeaders } > {
103
- let udi = `chia.${ this . storeId } ` ;
146
+ try {
147
+ let udi = `chia.${ this . storeId } ` ;
104
148
105
- if ( rootHash ) {
106
- udi += `.${ rootHash } ` ;
107
- }
149
+ if ( rootHash ) {
150
+ udi += `.${ rootHash } ` ;
151
+ }
108
152
109
- const url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /${ udi } /${ key } ` ;
110
- return this . head ( url ) ;
153
+ const url = `https://${ formatHost ( this . ipAddress ) } :${
154
+ ContentServer . port
155
+ } /${ udi } /${ key } `;
156
+ return this . head ( url ) ;
157
+ } catch ( error : any ) {
158
+ if ( rootHash ) {
159
+ hasRootHashCache . del ( `${ this . storeId } -${ rootHash } ` ) ;
160
+ }
161
+ throw error ;
162
+ }
111
163
}
112
164
113
165
// Method to check if a specific store exists (HEAD request)
114
166
public async headStore ( options ?: { hasRootHash : string } ) : Promise < {
115
167
success : boolean ;
116
168
headers ?: http . IncomingHttpHeaders ;
117
169
} > {
118
- let url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /chia.${ this . storeId } ` ;
170
+ try {
171
+ let url = `https://${ formatHost ( this . ipAddress ) } :${
172
+ ContentServer . port
173
+ } /chia.${ this . storeId } `;
119
174
120
- if ( options ?. hasRootHash ) {
121
- url += `?hasRootHash=${ options . hasRootHash } ` ;
122
- }
175
+ if ( options ?. hasRootHash ) {
176
+ url += `?hasRootHash=${ options . hasRootHash } ` ;
177
+ }
123
178
124
- return this . head ( url ) ;
179
+ return this . head ( url ) ;
180
+ } catch ( error : any ) {
181
+ if ( options ?. hasRootHash ) {
182
+ hasRootHashCache . del ( `${ this . storeId } -${ options . hasRootHash } ` ) ;
183
+ }
184
+ throw error ;
185
+ }
125
186
}
126
187
188
+ /**
189
+ * Checks if the store has the specified rootHash.
190
+ * Utilizes caching to improve performance.
191
+ *
192
+ * @param rootHash - The root hash to check.
193
+ * @returns A promise that resolves to true if the root hash exists, otherwise false.
194
+ */
127
195
public async hasRootHash ( rootHash : string ) : Promise < boolean > {
196
+ // Construct the cache key using storeId and rootHash
197
+ const cacheKey = `${ this . storeId } -${ rootHash } ` ;
198
+
199
+ // Check if the result is already cached
200
+ const cachedResult = hasRootHashCache . get < boolean > ( cacheKey ) ;
201
+ if ( cachedResult !== undefined ) {
202
+ return cachedResult ;
203
+ }
204
+
205
+ // If not cached, perform the headStore request
128
206
const { success, headers } = await this . headStore ( {
129
207
hasRootHash : rootHash ,
130
208
} ) ;
131
- if ( success ) {
132
- return headers ?. [ "x-has-root-hash" ] === "true" ;
209
+
210
+ // Determine if the store has the root hash
211
+ const hasHash = success && headers ?. [ "x-has-root-hash" ] === "true" ;
212
+
213
+ // Only cache the result if the store has the root hash
214
+ if ( hasHash ) {
215
+ hasRootHashCache . set ( cacheKey , true ) ;
133
216
}
134
- return false ;
217
+
218
+ return hasHash ;
135
219
}
136
220
137
221
public streamKey ( key : string , rootHash ?: string ) : Promise < Readable > {
@@ -142,7 +226,9 @@ export class ContentServer {
142
226
}
143
227
144
228
return new Promise ( ( resolve , reject ) => {
145
- const url = `https://${ formatHost ( this . ipAddress ) } :${ ContentServer . port } /${ udi } /${ key } ` ;
229
+ const url = `https://${ formatHost ( this . ipAddress ) } :${
230
+ ContentServer . port
231
+ } /${ udi } /${ key } `;
146
232
const urlObj = new URL ( url ) ;
147
233
148
234
const requestOptions = {
@@ -164,6 +250,7 @@ export class ContentServer {
164
250
reject ( new Error ( "Redirected without a location header" ) ) ;
165
251
}
166
252
} else {
253
+ hasRootHashCache . del ( `${ this . storeId } -${ rootHash } ` ) ;
167
254
reject (
168
255
new Error (
169
256
`Failed to retrieve data from ${ url } . Status code: ${ response . statusCode } `
@@ -483,13 +570,14 @@ export class ContentServer {
483
570
response . headers . location
484
571
) {
485
572
// Handle redirects
486
- const redirectUrl = new URL ( response . headers . location , url ) . toString ( ) ; // Resolve relative URLs
573
+ const redirectUrl = new URL (
574
+ response . headers . location ,
575
+ url
576
+ ) . toString ( ) ; // Resolve relative URLs
487
577
if ( timeout ) {
488
578
clearTimeout ( timeout ) ;
489
579
}
490
- this . fetchFirstChunk ( redirectUrl )
491
- . then ( resolve )
492
- . catch ( reject ) ;
580
+ this . fetchFirstChunk ( redirectUrl ) . then ( resolve ) . catch ( reject ) ;
493
581
} else {
494
582
if ( timeout ) {
495
583
clearTimeout ( timeout ) ;
0 commit comments