This repository was archived by the owner on Mar 4, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWebAsyncWrapper.cls
257 lines (219 loc) · 7.33 KB
/
WebAsyncWrapper.cls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
Persistable = 0 'NotPersistable
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "WebAsyncWrapper"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
''
' WebAsyncWrapper v4.1.6
' (c) Tim Hall - https://github.com/VBA-tools/VBA-Web
'
' Wrapper WebClient and WebRequest that enables callback-style async requests
'
' _Note_ Windows-only and Excel-only and requires reference to "Microsoft WinHTTP Services, version 5.1"
'
' Errors:
' 11050 / 80042b2a / -2147210454 - Client should not be changed
'
' @class WebAsyncWrapper
' @author [email protected]
' @license MIT (http://www.opensource.org/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
Option Explicit
' --------------------------------------------- '
' Constants and Private Variables
' --------------------------------------------- '
Private web_pClient As WebClient
' --------------------------------------------- '
' Properties
' --------------------------------------------- '
''
' Request that is currently executing.
'
' @property Request
' @type WebRequest
''
Public Request As WebRequest
''
' Function to call with response when request has completed.
'
' @property Callback
' @type String
''
Public Callback As String
''
' Array of arguments to pass to callback along with response
'
' @property CallbackArgs
' @type Variant
''
Public CallbackArgs As Variant
''
' @property Http
' @type WebHttpRequest
''
Public WithEvents Http As WinHttpRequest
Attribute Http.VB_VarHelpID = -1
''
' Client used for executing requests
'
' @property Client
' @type WebClient
' @throws 11050 / 80042b2a / -2147210454 - Client should not be changed
''
Public Property Get Client() As WebClient
Set Client = web_pClient
End Property
Public Property Set Client(Value As WebClient)
If web_pClient Is Nothing Or Value Is Nothing Then
Set web_pClient = Value
Else
' If a Client is changed while other Requests are executing, it may introduce unexpected behavior
' Guard against changing Client and instead recommend creating a new AsyncWrapper per Client
Dim web_ErrorDescription As String
web_ErrorDescription = "The Client for a WebAsyncWrapper should not be changed as it may affect any currently executing Requests. " & _
"A new WebAsyncWrapper should be created for each WebClient."
WebHelpers.LogError web_ErrorDescription, "WebAsyncWrapper.Client", vbObjectError + 11050
Err.Raise vbObjectError + 11050, "WebAsyncWrapper.Client", web_ErrorDescription
End If
End Property
' ============================================= '
' Public Methods
' ============================================= '
''
' Execute the specified request asynchronously
'
' @method ExecuteAsync
' @param {WebRequest} Request The request to execute
' @param {String} Callback Name of function to call when request completes
' @param {Variant} [CallbackArgs] Variable array of arguments that get passed directly to callback function
''
Public Sub ExecuteAsync(Request As WebRequest, Callback As String, Optional ByVal CallbackArgs As Variant)
' In order for AsyncWrapper to be reusable, clone then execute with clone
' - AsyncWrapper can only watch one WinHttpRequest's events
' - Callback + CallbackArgs would need to be stored per Request
Dim web_Async As WebAsyncWrapper
Set web_Async = Me.Clone
web_Async.PrepareAndExecuteRequest Request, Callback, CallbackArgs
End Sub
''
' Clone wrapper
'
' @internal
' @method Clone
' @return WebAsyncWrapper
''
Public Function Clone() As WebAsyncWrapper
Set Clone = New WebAsyncWrapper
Set Clone.Client = Me.Client
End Function
''
' Once everything has been prepared, execute request
'
' @internal
' @method PrepareAndExecuteRequest
' @param {WebRequest} Request
' @param {String} Callback
' @param {Variant} [CallbackArgs]
''
Public Sub PrepareAndExecuteRequest(Request As WebRequest, Callback As String, Optional ByVal CallbackArgs As Variant)
On Error GoTo web_ErrorHandling
Me.Callback = Callback
Me.CallbackArgs = CallbackArgs
Set Me.Request = Request.Clone
Set Me.Http = Me.Client.PrepareHttpRequest(Request)
web_StartTimeoutTimer
Me.Http.Send Request.Body
Exit Sub
web_ErrorHandling:
Set Me.Http = Nothing
Set Me.Request = Nothing
' Rethrow error
Err.Raise Err.Number, Err.Source, Err.Description
End Sub
''
' Handle timeout
'
' @internal
' @method TimedOut
''
Public Sub TimedOut()
Dim web_Response As New WebResponse
web_StopTimeoutTimer
WebHelpers.LogDebug "Timed out", "WebAsyncWrapper.TimedOut"
' Callback
web_Response.StatusCode = WebStatusCode.RequestTimeout
web_Response.StatusDescription = "Request Timeout"
web_RunCallback web_Response
End Sub
' ============================================= '
' Private Functions
' ============================================= '
Private Sub web_RunCallback(web_Response As WebResponse)
' Run callback function (needs to be a public function),
' passing in response and any defined callback arguments
'
' callback({WebResponse})
' OR callback({WebResponse}, {Variant})
'
' Example:
' Public Function Callback(Response As WebResponse, Args As Variant)
' Debug.Print "Callback: " & response.StatusCode
' For i = LBound(args) To UBound(args)
' Debug.Print args(i) & " was passed into async execute"
' Next i
' End Function
WebHelpers.LogResponse Me.Client, Me.Request, web_Response
If Not Me.Client.Authenticator Is Nothing Then
Me.Client.Authenticator.AfterExecute Me.Client, Me.Request, web_Response
End If
If Me.Callback <> "" Then
WebHelpers.LogDebug Me.Callback, "WebAsyncWrapper.RunCallback"
If Not IsMissing(Me.CallbackArgs) Then
Application.Run Me.Callback, web_Response, Me.CallbackArgs
Else
Application.Run Me.Callback, web_Response
End If
End If
Set Me.Http = Nothing
Set Me.Request = Nothing
End Sub
' Start timeout timer
Private Sub web_StartTimeoutTimer()
Dim web_TimeoutS As Long
If WebHelpers.AsyncRequests Is Nothing Then: Set WebHelpers.AsyncRequests = New Dictionary
' Round ms to seconds with minimum of 1 second if ms > 0
web_TimeoutS = Round(Me.Client.TimeoutMs / 1000, 0)
If Me.Client.TimeoutMs > 0 And web_TimeoutS = 0 Then
web_TimeoutS = 1
End If
WebHelpers.AsyncRequests.Add Me.Request.Id, Me
Application.OnTime Now + TimeValue("00:00:" & web_TimeoutS), "'WebHelpers.OnTimeoutTimerExpired """ & Me.Request.Id & """'"
End Sub
' Stop timeout timer
Private Sub web_StopTimeoutTimer()
If Not WebHelpers.AsyncRequests Is Nothing And Not Me.Request Is Nothing Then
If WebHelpers.AsyncRequests.Exists(Me.Request.Id) Then
WebHelpers.AsyncRequests.Remove Me.Request.Id
End If
End If
End Sub
' Process asynchronous requests
Private Sub Http_OnResponseFinished()
Dim web_Response As New WebResponse
web_StopTimeoutTimer
' Callback
web_Response.CreateFromHttp Me.Client, Me.Request, Me.Http
web_RunCallback web_Response
End Sub
Private Sub Class_Terminate()
Set Me.Client = Nothing
Set Me.Request = Nothing
End Sub