25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
9.2KB

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

Powered by TurnKey Linux.