You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 line
7.7KB

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

Powered by TurnKey Linux.