Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

237 wiersze
7.5KB

  1. <?xml version="1.0"?>
  2. <?component error="true" debug="false"?>
  3. <component>
  4. <registration
  5. description="WscMvc Application bootstrap"
  6. progid="WscMvc.Application"
  7. version="1.00"
  8. classid="{851C7763-1638-42FE-A166-BF3DD3A96A88}">
  9. </registration>
  10. <public>
  11. <method name="Run">
  12. <parameter name="ctx"/>
  13. <parameter name="statusLine"/>
  14. <parameter name="contentType"/>
  15. <parameter name="body"/>
  16. </method>
  17. </public>
  18. <script language="VBScript">
  19. <![CDATA[
  20. Option Explicit
  21. ' Central request boundary: one place decides expected (404) vs unexpected
  22. ' (COM/method failure -> 500) outcomes, and one place logs them. ctx is our
  23. ' own WscMvc.RequestContext object (not an ASP intrinsic), carrying only
  24. ' primitive request data. No ASP intrinsics are referenced here.
  25. Sub Run(ctx, statusLine, contentType, body)
  26. Dim ctrl, helloBody, path
  27. path = ctx.Path
  28. If path = "/hello" Then
  29. Set ctrl = Nothing
  30. On Error Resume Next
  31. Set ctrl = CreateObject("WscMvc.HomeController")
  32. If Err.Number <> 0 Then
  33. Err.Clear
  34. On Error Goto 0
  35. statusLine = "500 Internal Server Error"
  36. contentType = "text/plain; charset=utf-8"
  37. body = "Internal Server Error"
  38. LogOutcome ctx, statusLine
  39. Exit Sub
  40. End If
  41. On Error Goto 0
  42. helloBody = ""
  43. On Error Resume Next
  44. ctrl.Hello helloBody
  45. If Err.Number <> 0 Then
  46. Err.Clear
  47. On Error Goto 0
  48. statusLine = "500 Internal Server Error"
  49. contentType = "text/plain; charset=utf-8"
  50. body = "Internal Server Error"
  51. Set ctrl = Nothing
  52. LogOutcome ctx, statusLine
  53. Exit Sub
  54. End If
  55. On Error Goto 0
  56. statusLine = "200 OK"
  57. contentType = "text/html; charset=utf-8"
  58. body = helloBody
  59. Set ctrl = Nothing
  60. ElseIf path = "/self-test" Then
  61. Set ctrl = Nothing
  62. On Error Resume Next
  63. Set ctrl = CreateObject("WscMvc.SelfTestController")
  64. If Err.Number <> 0 Then
  65. Err.Clear
  66. On Error Goto 0
  67. statusLine = "500 Internal Server Error"
  68. contentType = "text/plain; charset=utf-8"
  69. body = "Internal Server Error"
  70. LogOutcome ctx, statusLine
  71. Exit Sub
  72. End If
  73. On Error Goto 0
  74. Dim selfTestBody
  75. selfTestBody = ""
  76. On Error Resume Next
  77. ctrl.RunSelfTest ctx.LogDir, selfTestBody
  78. If Err.Number <> 0 Then
  79. Err.Clear
  80. On Error Goto 0
  81. statusLine = "500 Internal Server Error"
  82. contentType = "text/plain; charset=utf-8"
  83. body = "Internal Server Error"
  84. Set ctrl = Nothing
  85. LogOutcome ctx, statusLine
  86. Exit Sub
  87. End If
  88. On Error Goto 0
  89. ' Always 200: the self-test HTTP call itself succeeded. Whether the
  90. ' underlying checks passed is reported in the JSON body's "ok" field
  91. ' and per-check "pass" fields, not the HTTP status - this matches
  92. ' conventional health-check endpoint design (5xx is reserved for the
  93. ' diagnostics mechanism itself being broken, handled above).
  94. statusLine = "200 OK"
  95. contentType = "application/json; charset=utf-8"
  96. body = selfTestBody
  97. Set ctrl = Nothing
  98. Else
  99. ' Expected outcome, not a failure: no matching route yet (M3 adds a real table).
  100. statusLine = "404 Not Found"
  101. contentType = "text/plain; charset=utf-8"
  102. body = "Not Found"
  103. End If
  104. LogOutcome ctx, statusLine
  105. End Sub
  106. ' Best-effort diagnostics only: a logging failure must never affect the
  107. ' response. Every filesystem step is individually guarded so one bad
  108. ' operation (e.g. concurrent-write contention) just skips this line rather
  109. ' than raising to the caller.
  110. Sub LogOutcome(ctx, statusLine)
  111. Dim fso, logFile, logPath, lockPath, line
  112. Set fso = Nothing
  113. On Error Resume Next
  114. Set fso = CreateObject("Scripting.FileSystemObject")
  115. If Err.Number <> 0 Then
  116. Err.Clear
  117. On Error Goto 0
  118. Exit Sub
  119. End If
  120. On Error Goto 0
  121. On Error Resume Next
  122. If Not fso.FolderExists(ctx.LogDir) Then
  123. fso.CreateFolder ctx.LogDir
  124. End If
  125. If Err.Number <> 0 Then
  126. Err.Clear
  127. On Error Goto 0
  128. Set fso = Nothing
  129. Exit Sub
  130. End If
  131. On Error Goto 0
  132. logPath = ctx.LogDir & "\app.log"
  133. lockPath = ctx.LogDir & "\app.log.lock"
  134. ' Concurrent requests race for this one shared file. Verified experimentally
  135. ' that FileSystemObject.OpenTextFile(ForAppending) mostly SUCCEEDS for every
  136. ' concurrent caller (it is not exclusive) but their writes overwrite each
  137. ' other - a lost-update race, not an open failure - so retrying the open
  138. ' alone did not help. No ASP intrinsic (Application.Lock/UnLock) is available
  139. ' here by contract, so a manual lock-file mutex is used instead:
  140. ' CreateTextFile(path, OverwriteExisting:=False) atomically fails with
  141. ' Err.Number=58 "File already exists" when another request holds the lock -
  142. ' confirmed correct/exclusive experimentally, not assumed.
  143. '
  144. ' The retry budget below is deliberately short. Measured under 8 genuinely
  145. ' concurrent requests: the mutex itself is correct, but making every request
  146. ' reliably win eventually needs a retry budget of 300+ attempts (~300-360ms
  147. ' of blocking) - too much added latency for a best-effort diagnostic write.
  148. ' With this short budget, heavy concurrent bursts will legitimately drop
  149. ' some log lines rather than delay the response; this is an accepted
  150. ' tradeoff for "best-effort diagnostics" (see docs/DECISIONS.md), not a
  151. ' correctness bug - the HTTP response itself is unaffected either way.
  152. ' Revisit with a real logging mechanism (e.g. per-request files aggregated
  153. ' out of band) in M6 if complete log coverage under load becomes a
  154. ' requirement.
  155. If Not AcquireLogLock(fso, lockPath) Then
  156. Set fso = Nothing
  157. Exit Sub
  158. End If
  159. Set logFile = Nothing
  160. On Error Resume Next
  161. Set logFile = fso.OpenTextFile(logPath, 8, True) ' 8 = ForAppending, create if missing
  162. If Err.Number <> 0 Then
  163. Err.Clear
  164. On Error Goto 0
  165. ReleaseLogLock fso, lockPath
  166. Set fso = Nothing
  167. Exit Sub
  168. End If
  169. On Error Goto 0
  170. line = Now & " | " & ctx.CorrelationId & " | " & ctx.HttpMethod & _
  171. " | " & ctx.Path & " | " & statusLine & " | " & ctx.ElapsedMs() & "ms"
  172. On Error Resume Next
  173. logFile.WriteLine line
  174. Err.Clear
  175. On Error Goto 0
  176. On Error Resume Next
  177. logFile.Close
  178. Err.Clear
  179. On Error Goto 0
  180. Set logFile = Nothing
  181. ReleaseLogLock fso, lockPath
  182. Set fso = Nothing
  183. End Sub
  184. Function AcquireLogLock(fso, lockPath)
  185. Dim attempt, maxAttempts, lockFile, busy, spinCount
  186. maxAttempts = 10
  187. spinCount = 8000
  188. AcquireLogLock = False
  189. For attempt = 1 To maxAttempts
  190. Set lockFile = Nothing
  191. On Error Resume Next
  192. Set lockFile = fso.CreateTextFile(lockPath, False) ' fails if lockPath already exists
  193. If Err.Number = 0 Then
  194. On Error Goto 0
  195. lockFile.Close
  196. Set lockFile = Nothing
  197. AcquireLogLock = True
  198. Exit For
  199. End If
  200. Err.Clear
  201. On Error Goto 0
  202. For busy = 1 To spinCount : Next
  203. Next
  204. End Function
  205. Sub ReleaseLogLock(fso, lockPath)
  206. On Error Resume Next
  207. fso.DeleteFile lockPath, True
  208. Err.Clear
  209. On Error Goto 0
  210. End Sub
  211. ]]>
  212. </script>
  213. </component>

Powered by TurnKey Linux.