No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

301 líneas
9.1KB

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

Powered by TurnKey Linux.