We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
CORS support in backend
I want to test a frontend on a different port than the backend on `localhost`, but get blocked by CORS.

Sadly however `aiohttp_cors` has a 5-year-old bug that breaks CORS support for POST requests (while GET works fine): https://github.com/aio-libs/aiohttp-cors/issues/155

I found that I had to use this code to work around it, but I couldn't get around modifying the backend https://github.com/home-assistant/core/issues/40513#issuecomment-700654471

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `server.py`
Content:
```
1 import os
2 import sys
3 import asyncio
4 import nodes
5 import folder_paths
6 import execution
7 import uuid
8 import json
9 import glob
10 try:
11     import aiohttp
12     from aiohttp import web
13 except ImportError:
14     print("Module 'aiohttp' not installed. Please install it via:")
15     print("pip install aiohttp")
16     print("or")
17     print("pip install -r requirements.txt")
18     sys.exit()
19 
20 import mimetypes
21 
22 
23 @web.middleware
24 async def cache_control(request: web.Request, handler):
25     response: web.Response = await handler(request)
26     if request.path.endswith('.js') or request.path.endswith('.css'):
27         response.headers.setdefault('Cache-Control', 'no-cache')
28     return response
29 
30 class PromptServer():
31     def __init__(self, loop):
32         PromptServer.instance = self
33 
34         mimetypes.init(); 
35         mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'
36         self.prompt_queue = None
37         self.loop = loop
38         self.messages = asyncio.Queue()
39         self.number = 0
40         self.app = web.Application(client_max_size=20971520, middlewares=[cache_control])
41         self.sockets = dict()
42         self.web_root = os.path.join(os.path.dirname(
43             os.path.realpath(__file__)), "web")
44         routes = web.RouteTableDef()
45         self.routes = routes
46         self.last_node_id = None
47         self.client_id = None
48 
49         @routes.get('/ws')
50         async def websocket_handler(request):
51             ws = web.WebSocketResponse()
52             await ws.prepare(request)
53             sid = request.rel_url.query.get('clientId', '')
54             if sid:
55                 # Reusing existing session, remove old
56                 self.sockets.pop(sid, None)
57             else:
58                 sid = uuid.uuid4().hex      
59 
60             self.sockets[sid] = ws
61 
62             try:
63                 # Send initial state to the new client
64                 await self.send("status", { "status": self.get_queue_info(), 'sid': sid }, sid)
65                 # On reconnect if we are the currently executing client send the current node
66                 if self.client_id == sid and self.last_node_id is not None:
67                     await self.send("executing", { "node": self.last_node_id }, sid)
68                     
69                 async for msg in ws:
70                     if msg.type == aiohttp.WSMsgType.ERROR:
71                         print('ws connection closed with exception %s' % ws.exception())
72             finally:
73                 self.sockets.pop(sid, None)
74             return ws
75 
76         @routes.get("/")
77         async def get_root(request):
78             return web.FileResponse(os.path.join(self.web_root, "index.html"))
79 
80         @routes.get("/embeddings")
81         def get_embeddings(self):
82             embeddings = folder_paths.get_filename_list("embeddings")
83             return web.json_response(list(map(lambda a: os.path.splitext(a)[0].lower(), embeddings)))
84 
85         @routes.get("/extensions")
86         async def get_extensions(request):
87             files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)
88             return web.json_response(list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files)))
89 
90         @routes.post("/upload/image")
91         async def upload_image(request):
92             upload_dir = folder_paths.get_input_directory()
93 
94             if not os.path.exists(upload_dir):
95                 os.makedirs(upload_dir)
96             
97             post = await request.post()
98             image = post.get("image")
99 
100             if image and image.file:
101                 filename = image.filename
102                 if not filename:
103                     return web.Response(status=400)
104 
105                 split = os.path.splitext(filename)
106                 i = 1
107                 while os.path.exists(os.path.join(upload_dir, filename)):
108                     filename = f"{split[0]} ({i}){split[1]}"
109                     i += 1
110 
111                 filepath = os.path.join(upload_dir, filename)
112 
113                 with open(filepath, "wb") as f:
114                     f.write(image.file.read())
115                 
116                 return web.json_response({"name" : filename})
117             else:
118                 return web.Response(status=400)
119 
120 
121         @routes.get("/view")
122         async def view_image(request):
123             if "filename" in request.rel_url.query:
124                 type = request.rel_url.query.get("type", "output")
125                 output_dir = folder_paths.get_directory_by_type(type)
126                 if output_dir is None:
127                     return web.Response(status=400)
128 
129                 if "subfolder" in request.rel_url.query:
130                     full_output_dir = os.path.join(output_dir, request.rel_url.query["subfolder"])
131                     if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:
132                         return web.Response(status=403)
133                     output_dir = full_output_dir
134 
135                 filename = request.rel_url.query["filename"]
136                 filename = os.path.basename(filename)
137                 file = os.path.join(output_dir, filename)
138 
139                 if os.path.isfile(file):
140                     return web.FileResponse(file, headers={"Content-Disposition": f"filename=\"{filename}\""})
141                 
142             return web.Response(status=404)
143 
144         @routes.get("/prompt")
145         async def get_prompt(request):
146             return web.json_response(self.get_queue_info())
147 
148         @routes.get("/object_info")
149         async def get_object_info(request):
150             out = {}
151             for x in nodes.NODE_CLASS_MAPPINGS:
152                 obj_class = nodes.NODE_CLASS_MAPPINGS[x]
153                 info = {}
154                 info['input'] = obj_class.INPUT_TYPES()
155                 info['output'] = obj_class.RETURN_TYPES
156                 info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']
157                 info['name'] = x #TODO
158                 info['description'] = ''
159                 info['category'] = 'sd'
160                 if hasattr(obj_class, 'CATEGORY'):
161                     info['category'] = obj_class.CATEGORY
162                 out[x] = info
163             return web.json_response(out)
164 
165         @routes.get("/history")
166         async def get_history(request):
167             return web.json_response(self.prompt_queue.get_history())
168 
169         @routes.get("/queue")
170         async def get_queue(request):
171             queue_info = {}
172             current_queue = self.prompt_queue.get_current_queue()
173             queue_info['queue_running'] = current_queue[0]
174             queue_info['queue_pending'] = current_queue[1]
175             return web.json_response(queue_info)
176 
177         @routes.post("/prompt")
178         async def post_prompt(request):
179             print("got prompt")
180             resp_code = 200
181             out_string = ""
182             json_data =  await request.json()
183 
184             if "number" in json_data:
185                 number = float(json_data['number'])
186             else:
187                 number = self.number
188                 if "front" in json_data:
189                     if json_data['front']:
190                         number = -number
191 
192                 self.number += 1
193 
194             if "prompt" in json_data:
195                 prompt = json_data["prompt"]
196                 valid = execution.validate_prompt(prompt)
197                 extra_data = {}
198                 if "extra_data" in json_data:
199                     extra_data = json_data["extra_data"]
200 
201                 if "client_id" in json_data:
202                     extra_data["client_id"] = json_data["client_id"]
203                 if valid[0]:
204                     self.prompt_queue.put((number, id(prompt), prompt, extra_data))
205                 else:
206                     resp_code = 400
207                     out_string = valid[1]
208                     print("invalid prompt:", valid[1])
209 
210             return web.Response(body=out_string, status=resp_code)
211         
212         @routes.post("/queue")
213         async def post_queue(request):
214             json_data =  await request.json()
215             if "clear" in json_data:
216                 if json_data["clear"]:
217                     self.prompt_queue.wipe_queue()
218             if "delete" in json_data:
219                 to_delete = json_data['delete']
220                 for id_to_delete in to_delete:
221                     delete_func = lambda a: a[1] == int(id_to_delete)
222                     self.prompt_queue.delete_queue_item(delete_func)
223                     
224             return web.Response(status=200)
225 
226         @routes.post("/interrupt")
227         async def post_interrupt(request):
228             nodes.interrupt_processing()
229             return web.Response(status=200)
230 
231         @routes.post("/history")
232         async def post_history(request):
233             json_data =  await request.json()
234             if "clear" in json_data:
235                 if json_data["clear"]:
236                     self.prompt_queue.wipe_history()
237             if "delete" in json_data:
238                 to_delete = json_data['delete']
239                 for id_to_delete in to_delete:
240                     self.prompt_queue.delete_history_item(id_to_delete)
241 
242             return web.Response(status=200)
243         
244     def add_routes(self):
245         self.app.add_routes(self.routes)
246         self.app.add_routes([
247             web.static('/', self.web_root),
248         ])
249 
250     def get_queue_info(self):
251         prompt_info = {}
252         exec_info = {}
253         exec_info['queue_remaining'] = self.prompt_queue.get_tasks_remaining()
254         prompt_info['exec_info'] = exec_info
255         return prompt_info
256 
257     async def send(self, event, data, sid=None):
258         message = {"type": event, "data": data}
259        
260         if isinstance(message, str) == False:
261             message = json.dumps(message)
262 
263         if sid is None:
264             for ws in self.sockets.values():
265                 await ws.send_str(message)
266         elif sid in self.sockets:
267             await self.sockets[sid].send_str(message)
268 
269     def send_sync(self, event, data, sid=None):
270         self.loop.call_soon_threadsafe(
271             self.messages.put_nowait, (event, data, sid))
272 
273     def queue_updated(self):
274         self.send_sync("status", { "status": self.get_queue_info() })
275 
276     async def publish_loop(self):
277         while True:
278             msg = await self.messages.get()
279             await self.send(*msg)
280 
281     async def start(self, address, port, verbose=True, call_on_start=None):
282         runner = web.AppRunner(self.app)
283         await runner.setup()
284         site = web.TCPSite(runner, address, port)
285         await site.start()
286 
287         if address == '':
288             address = '0.0.0.0'
289         if verbose:
290             print("Starting server\n")
291             print("To see the GUI go to: http://{}:{}".format(address, port))
292         if call_on_start is not None:
293             call_on_start(address, port)
294 
295
```

Path: `comfy/cli_args.py`
Content:
```
1 import argparse
2 
3 parser = argparse.ArgumentParser()
4 
5 parser.add_argument("--listen", nargs="?", const="0.0.0.0", default="127.0.0.1", type=str, help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)")
6 parser.add_argument("--port", type=int, default=8188, help="Set the listen port.")
7 parser.add_argument("--extra-model-paths-config", type=str, default=None, help="Load an extra_model_paths.yaml file.")
8 parser.add_argument("--output-directory", type=str, default=None, help="Set the ComfyUI output directory.")
9 parser.add_argument("--dont-upcast-attention", action="store_true", help="Disable upcasting of attention. Can boost speed but increase the chances of black images.")
10 
11 attn_group = parser.add_mutually_exclusive_group()
12 attn_group.add_argument("--use-split-cross-attention", action="store_true", help="Use the split cross attention optimization instead of the sub-quadratic one. Ignored when xformers is used.")
13 attn_group.add_argument("--use-pytorch-cross-attention", action="store_true", help="Use the new pytorch 2.0 cross attention function.")
14 
15 parser.add_argument("--disable-xformers", action="store_true", help="Disable xformers.")
16 parser.add_argument("--cuda-device", type=int, default=None, help="Set the id of the cuda device this instance will use.")
17 
18 vram_group = parser.add_mutually_exclusive_group()
19 vram_group.add_argument("--highvram", action="store_true", help="By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory.")
20 vram_group.add_argument("--normalvram", action="store_true", help="Used to force normal vram use if lowvram gets automatically enabled.")
21 vram_group.add_argument("--lowvram", action="store_true", help="Split the unet in parts to use less vram.")
22 vram_group.add_argument("--novram", action="store_true", help="When lowvram isn't enough.")
23 vram_group.add_argument("--cpu", action="store_true", help="To use the CPU for everything (slow).")
24 
25 parser.add_argument("--dont-print-server", action="store_true", help="Don't print server output.")
26 parser.add_argument("--quick-test-for-ci", action="store_true", help="Quick test for CI.")
27 parser.add_argument("--windows-standalone-build", action="store_true", help="Windows standalone build.")
28 
29 args = parser.parse_args()
30
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/comfy/cli_args.py b/comfy/cli_args.py
--- a/comfy/cli_args.py
+++ b/comfy/cli_args.py
@@ -4,8 +4,10 @@
 
 parser.add_argument("--listen", nargs="?", const="0.0.0.0", default="127.0.0.1", type=str, help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)")
 parser.add_argument("--port", type=int, default=8188, help="Set the listen port.")
+parser.add_argument("--cors", default=None, nargs="?", const="*", help="Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.")
 parser.add_argument("--extra-model-paths-config", type=str, default=None, help="Load an extra_model_paths.yaml file.")
 parser.add_argument("--output-directory", type=str, default=None, help="Set the ComfyUI output directory.")
+parser.add_argument("--cuda-device", type=int, default=None, help="Set the id of the cuda device this instance will use.")
 parser.add_argument("--dont-upcast-attention", action="store_true", help="Disable upcasting of attention. Can boost speed but increase the chances of black images.")
 
 attn_group = parser.add_mutually_exclusive_group()
@@ -13,7 +15,6 @@
 attn_group.add_argument("--use-pytorch-cross-attention", action="store_true", help="Use the new pytorch 2.0 cross attention function.")
 
 parser.add_argument("--disable-xformers", action="store_true", help="Disable xformers.")
-parser.add_argument("--cuda-device", type=int, default=None, help="Set the id of the cuda device this instance will use.")
 
 vram_group = parser.add_mutually_exclusive_group()
 vram_group.add_argument("--highvram", action="store_true", help="By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory.")
diff --git a/server.py b/server.py
--- a/server.py
+++ b/server.py
@@ -18,6 +18,7 @@
     sys.exit()
 
 import mimetypes
+from comfy.cli_args import args
 
 
 @web.middleware
@@ -27,6 +28,23 @@
         response.headers.setdefault('Cache-Control', 'no-cache')
     return response
 
+def create_cors_middleware(allowed_origin: str):
+    @web.middleware
+    async def cors_middleware(request: web.Request, handler):
+        if request.method == "OPTIONS":
+            # Pre-flight request. Reply successfully:
+            response = web.Response()
+        else:
+            response = await handler(request)
+
+        response.headers['Access-Control-Allow-Origin'] = allowed_origin
+        response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, OPTIONS'
+        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
+        response.headers['Access-Control-Allow-Credentials'] = 'true'
+        return response
+
+    return cors_middleware
+
 class PromptServer():
     def __init__(self, loop):
         PromptServer.instance = self
@@ -37,7 +55,12 @@
         self.loop = loop
         self.messages = asyncio.Queue()
         self.number = 0
-        self.app = web.Application(client_max_size=20971520, middlewares=[cache_control])
+
+        middlewares = [cache_control]
+        if args.cors:
+            middlewares.append(create_cors_middleware(args.cors))
+
+        self.app = web.Application(client_max_size=20971520, middlewares=middlewares)
         self.sockets = dict()
         self.web_root = os.path.join(os.path.dirname(
             os.path.realpath(__file__)), "web")

{"golden_diff": "diff --git a/comfy/cli_args.py b/comfy/cli_args.py\n--- a/comfy/cli_args.py\n+++ b/comfy/cli_args.py\n@@ -4,8 +4,10 @@\n \n parser.add_argument(\"--listen\", nargs=\"?\", const=\"0.0.0.0\", default=\"127.0.0.1\", type=str, help=\"Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)\")\n parser.add_argument(\"--port\", type=int, default=8188, help=\"Set the listen port.\")\n+parser.add_argument(\"--cors\", default=None, nargs=\"?\", const=\"*\", help=\"Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.\")\n parser.add_argument(\"--extra-model-paths-config\", type=str, default=None, help=\"Load an extra_model_paths.yaml file.\")\n parser.add_argument(\"--output-directory\", type=str, default=None, help=\"Set the ComfyUI output directory.\")\n+parser.add_argument(\"--cuda-device\", type=int, default=None, help=\"Set the id of the cuda device this instance will use.\")\n parser.add_argument(\"--dont-upcast-attention\", action=\"store_true\", help=\"Disable upcasting of attention. Can boost speed but increase the chances of black images.\")\n \n attn_group = parser.add_mutually_exclusive_group()\n@@ -13,7 +15,6 @@\n attn_group.add_argument(\"--use-pytorch-cross-attention\", action=\"store_true\", help=\"Use the new pytorch 2.0 cross attention function.\")\n \n parser.add_argument(\"--disable-xformers\", action=\"store_true\", help=\"Disable xformers.\")\n-parser.add_argument(\"--cuda-device\", type=int, default=None, help=\"Set the id of the cuda device this instance will use.\")\n \n vram_group = parser.add_mutually_exclusive_group()\n vram_group.add_argument(\"--highvram\", action=\"store_true\", help=\"By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory.\")\ndiff --git a/server.py b/server.py\n--- a/server.py\n+++ b/server.py\n@@ -18,6 +18,7 @@\n     sys.exit()\n \n import mimetypes\n+from comfy.cli_args import args\n \n \n @web.middleware\n@@ -27,6 +28,23 @@\n         response.headers.setdefault('Cache-Control', 'no-cache')\n     return response\n \n+def create_cors_middleware(allowed_origin: str):\n+    @web.middleware\n+    async def cors_middleware(request: web.Request, handler):\n+        if request.method == \"OPTIONS\":\n+            # Pre-flight request. Reply successfully:\n+            response = web.Response()\n+        else:\n+            response = await handler(request)\n+\n+        response.headers['Access-Control-Allow-Origin'] = allowed_origin\n+        response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, OPTIONS'\n+        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'\n+        response.headers['Access-Control-Allow-Credentials'] = 'true'\n+        return response\n+\n+    return cors_middleware\n+\n class PromptServer():\n     def __init__(self, loop):\n         PromptServer.instance = self\n@@ -37,7 +55,12 @@\n         self.loop = loop\n         self.messages = asyncio.Queue()\n         self.number = 0\n-        self.app = web.Application(client_max_size=20971520, middlewares=[cache_control])\n+\n+        middlewares = [cache_control]\n+        if args.cors:\n+            middlewares.append(create_cors_middleware(args.cors))\n+\n+        self.app = web.Application(client_max_size=20971520, middlewares=middlewares)\n         self.sockets = dict()\n         self.web_root = os.path.join(os.path.dirname(\n             os.path.realpath(__file__)), \"web\")\n", "issue": "CORS support in backend\nI want to test a frontend on a different port than the backend on `localhost`, but get blocked by CORS.\r\n\r\nSadly however `aiohttp_cors` has a 5-year-old bug that breaks CORS support for POST requests (while GET works fine): https://github.com/aio-libs/aiohttp-cors/issues/155\r\n\r\nI found that I had to use this code to work around it, but I couldn't get around modifying the backend https://github.com/home-assistant/core/issues/40513#issuecomment-700654471\n", "before_files": [{"content": "import os\nimport sys\nimport asyncio\nimport nodes\nimport folder_paths\nimport execution\nimport uuid\nimport json\nimport glob\ntry:\n    import aiohttp\n    from aiohttp import web\nexcept ImportError:\n    print(\"Module 'aiohttp' not installed. Please install it via:\")\n    print(\"pip install aiohttp\")\n    print(\"or\")\n    print(\"pip install -r requirements.txt\")\n    sys.exit()\n\nimport mimetypes\n\n\n@web.middleware\nasync def cache_control(request: web.Request, handler):\n    response: web.Response = await handler(request)\n    if request.path.endswith('.js') or request.path.endswith('.css'):\n        response.headers.setdefault('Cache-Control', 'no-cache')\n    return response\n\nclass PromptServer():\n    def __init__(self, loop):\n        PromptServer.instance = self\n\n        mimetypes.init(); \n        mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'\n        self.prompt_queue = None\n        self.loop = loop\n        self.messages = asyncio.Queue()\n        self.number = 0\n        self.app = web.Application(client_max_size=20971520, middlewares=[cache_control])\n        self.sockets = dict()\n        self.web_root = os.path.join(os.path.dirname(\n            os.path.realpath(__file__)), \"web\")\n        routes = web.RouteTableDef()\n        self.routes = routes\n        self.last_node_id = None\n        self.client_id = None\n\n        @routes.get('/ws')\n        async def websocket_handler(request):\n            ws = web.WebSocketResponse()\n            await ws.prepare(request)\n            sid = request.rel_url.query.get('clientId', '')\n            if sid:\n                # Reusing existing session, remove old\n                self.sockets.pop(sid, None)\n            else:\n                sid = uuid.uuid4().hex      \n\n            self.sockets[sid] = ws\n\n            try:\n                # Send initial state to the new client\n                await self.send(\"status\", { \"status\": self.get_queue_info(), 'sid': sid }, sid)\n                # On reconnect if we are the currently executing client send the current node\n                if self.client_id == sid and self.last_node_id is not None:\n                    await self.send(\"executing\", { \"node\": self.last_node_id }, sid)\n                    \n                async for msg in ws:\n                    if msg.type == aiohttp.WSMsgType.ERROR:\n                        print('ws connection closed with exception %s' % ws.exception())\n            finally:\n                self.sockets.pop(sid, None)\n            return ws\n\n        @routes.get(\"/\")\n        async def get_root(request):\n            return web.FileResponse(os.path.join(self.web_root, \"index.html\"))\n\n        @routes.get(\"/embeddings\")\n        def get_embeddings(self):\n            embeddings = folder_paths.get_filename_list(\"embeddings\")\n            return web.json_response(list(map(lambda a: os.path.splitext(a)[0].lower(), embeddings)))\n\n        @routes.get(\"/extensions\")\n        async def get_extensions(request):\n            files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)\n            return web.json_response(list(map(lambda f: \"/\" + os.path.relpath(f, self.web_root).replace(\"\\\\\", \"/\"), files)))\n\n        @routes.post(\"/upload/image\")\n        async def upload_image(request):\n            upload_dir = folder_paths.get_input_directory()\n\n            if not os.path.exists(upload_dir):\n                os.makedirs(upload_dir)\n            \n            post = await request.post()\n            image = post.get(\"image\")\n\n            if image and image.file:\n                filename = image.filename\n                if not filename:\n                    return web.Response(status=400)\n\n                split = os.path.splitext(filename)\n                i = 1\n                while os.path.exists(os.path.join(upload_dir, filename)):\n                    filename = f\"{split[0]} ({i}){split[1]}\"\n                    i += 1\n\n                filepath = os.path.join(upload_dir, filename)\n\n                with open(filepath, \"wb\") as f:\n                    f.write(image.file.read())\n                \n                return web.json_response({\"name\" : filename})\n            else:\n                return web.Response(status=400)\n\n\n        @routes.get(\"/view\")\n        async def view_image(request):\n            if \"filename\" in request.rel_url.query:\n                type = request.rel_url.query.get(\"type\", \"output\")\n                output_dir = folder_paths.get_directory_by_type(type)\n                if output_dir is None:\n                    return web.Response(status=400)\n\n                if \"subfolder\" in request.rel_url.query:\n                    full_output_dir = os.path.join(output_dir, request.rel_url.query[\"subfolder\"])\n                    if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:\n                        return web.Response(status=403)\n                    output_dir = full_output_dir\n\n                filename = request.rel_url.query[\"filename\"]\n                filename = os.path.basename(filename)\n                file = os.path.join(output_dir, filename)\n\n                if os.path.isfile(file):\n                    return web.FileResponse(file, headers={\"Content-Disposition\": f\"filename=\\\"{filename}\\\"\"})\n                \n            return web.Response(status=404)\n\n        @routes.get(\"/prompt\")\n        async def get_prompt(request):\n            return web.json_response(self.get_queue_info())\n\n        @routes.get(\"/object_info\")\n        async def get_object_info(request):\n            out = {}\n            for x in nodes.NODE_CLASS_MAPPINGS:\n                obj_class = nodes.NODE_CLASS_MAPPINGS[x]\n                info = {}\n                info['input'] = obj_class.INPUT_TYPES()\n                info['output'] = obj_class.RETURN_TYPES\n                info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']\n                info['name'] = x #TODO\n                info['description'] = ''\n                info['category'] = 'sd'\n                if hasattr(obj_class, 'CATEGORY'):\n                    info['category'] = obj_class.CATEGORY\n                out[x] = info\n            return web.json_response(out)\n\n        @routes.get(\"/history\")\n        async def get_history(request):\n            return web.json_response(self.prompt_queue.get_history())\n\n        @routes.get(\"/queue\")\n        async def get_queue(request):\n            queue_info = {}\n            current_queue = self.prompt_queue.get_current_queue()\n            queue_info['queue_running'] = current_queue[0]\n            queue_info['queue_pending'] = current_queue[1]\n            return web.json_response(queue_info)\n\n        @routes.post(\"/prompt\")\n        async def post_prompt(request):\n            print(\"got prompt\")\n            resp_code = 200\n            out_string = \"\"\n            json_data =  await request.json()\n\n            if \"number\" in json_data:\n                number = float(json_data['number'])\n            else:\n                number = self.number\n                if \"front\" in json_data:\n                    if json_data['front']:\n                        number = -number\n\n                self.number += 1\n\n            if \"prompt\" in json_data:\n                prompt = json_data[\"prompt\"]\n                valid = execution.validate_prompt(prompt)\n                extra_data = {}\n                if \"extra_data\" in json_data:\n                    extra_data = json_data[\"extra_data\"]\n\n                if \"client_id\" in json_data:\n                    extra_data[\"client_id\"] = json_data[\"client_id\"]\n                if valid[0]:\n                    self.prompt_queue.put((number, id(prompt), prompt, extra_data))\n                else:\n                    resp_code = 400\n                    out_string = valid[1]\n                    print(\"invalid prompt:\", valid[1])\n\n            return web.Response(body=out_string, status=resp_code)\n        \n        @routes.post(\"/queue\")\n        async def post_queue(request):\n            json_data =  await request.json()\n            if \"clear\" in json_data:\n                if json_data[\"clear\"]:\n                    self.prompt_queue.wipe_queue()\n            if \"delete\" in json_data:\n                to_delete = json_data['delete']\n                for id_to_delete in to_delete:\n                    delete_func = lambda a: a[1] == int(id_to_delete)\n                    self.prompt_queue.delete_queue_item(delete_func)\n                    \n            return web.Response(status=200)\n\n        @routes.post(\"/interrupt\")\n        async def post_interrupt(request):\n            nodes.interrupt_processing()\n            return web.Response(status=200)\n\n        @routes.post(\"/history\")\n        async def post_history(request):\n            json_data =  await request.json()\n            if \"clear\" in json_data:\n                if json_data[\"clear\"]:\n                    self.prompt_queue.wipe_history()\n            if \"delete\" in json_data:\n                to_delete = json_data['delete']\n                for id_to_delete in to_delete:\n                    self.prompt_queue.delete_history_item(id_to_delete)\n\n            return web.Response(status=200)\n        \n    def add_routes(self):\n        self.app.add_routes(self.routes)\n        self.app.add_routes([\n            web.static('/', self.web_root),\n        ])\n\n    def get_queue_info(self):\n        prompt_info = {}\n        exec_info = {}\n        exec_info['queue_remaining'] = self.prompt_queue.get_tasks_remaining()\n        prompt_info['exec_info'] = exec_info\n        return prompt_info\n\n    async def send(self, event, data, sid=None):\n        message = {\"type\": event, \"data\": data}\n       \n        if isinstance(message, str) == False:\n            message = json.dumps(message)\n\n        if sid is None:\n            for ws in self.sockets.values():\n                await ws.send_str(message)\n        elif sid in self.sockets:\n            await self.sockets[sid].send_str(message)\n\n    def send_sync(self, event, data, sid=None):\n        self.loop.call_soon_threadsafe(\n            self.messages.put_nowait, (event, data, sid))\n\n    def queue_updated(self):\n        self.send_sync(\"status\", { \"status\": self.get_queue_info() })\n\n    async def publish_loop(self):\n        while True:\n            msg = await self.messages.get()\n            await self.send(*msg)\n\n    async def start(self, address, port, verbose=True, call_on_start=None):\n        runner = web.AppRunner(self.app)\n        await runner.setup()\n        site = web.TCPSite(runner, address, port)\n        await site.start()\n\n        if address == '':\n            address = '0.0.0.0'\n        if verbose:\n            print(\"Starting server\\n\")\n            print(\"To see the GUI go to: http://{}:{}\".format(address, port))\n        if call_on_start is not None:\n            call_on_start(address, port)\n\n", "path": "server.py"}, {"content": "import argparse\n\nparser = argparse.ArgumentParser()\n\nparser.add_argument(\"--listen\", nargs=\"?\", const=\"0.0.0.0\", default=\"127.0.0.1\", type=str, help=\"Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)\")\nparser.add_argument(\"--port\", type=int, default=8188, help=\"Set the listen port.\")\nparser.add_argument(\"--extra-model-paths-config\", type=str, default=None, help=\"Load an extra_model_paths.yaml file.\")\nparser.add_argument(\"--output-directory\", type=str, default=None, help=\"Set the ComfyUI output directory.\")\nparser.add_argument(\"--dont-upcast-attention\", action=\"store_true\", help=\"Disable upcasting of attention. Can boost speed but increase the chances of black images.\")\n\nattn_group = parser.add_mutually_exclusive_group()\nattn_group.add_argument(\"--use-split-cross-attention\", action=\"store_true\", help=\"Use the split cross attention optimization instead of the sub-quadratic one. Ignored when xformers is used.\")\nattn_group.add_argument(\"--use-pytorch-cross-attention\", action=\"store_true\", help=\"Use the new pytorch 2.0 cross attention function.\")\n\nparser.add_argument(\"--disable-xformers\", action=\"store_true\", help=\"Disable xformers.\")\nparser.add_argument(\"--cuda-device\", type=int, default=None, help=\"Set the id of the cuda device this instance will use.\")\n\nvram_group = parser.add_mutually_exclusive_group()\nvram_group.add_argument(\"--highvram\", action=\"store_true\", help=\"By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory.\")\nvram_group.add_argument(\"--normalvram\", action=\"store_true\", help=\"Used to force normal vram use if lowvram gets automatically enabled.\")\nvram_group.add_argument(\"--lowvram\", action=\"store_true\", help=\"Split the unet in parts to use less vram.\")\nvram_group.add_argument(\"--novram\", action=\"store_true\", help=\"When lowvram isn't enough.\")\nvram_group.add_argument(\"--cpu\", action=\"store_true\", help=\"To use the CPU for everything (slow).\")\n\nparser.add_argument(\"--dont-print-server\", action=\"store_true\", help=\"Don't print server output.\")\nparser.add_argument(\"--quick-test-for-ci\", action=\"store_true\", help=\"Quick test for CI.\")\nparser.add_argument(\"--windows-standalone-build\", action=\"store_true\", help=\"Windows standalone build.\")\n\nargs = parser.parse_args()\n", "path": "comfy/cli_args.py"}], "after_files": [{"content": "import os\nimport sys\nimport asyncio\nimport nodes\nimport folder_paths\nimport execution\nimport uuid\nimport json\nimport glob\ntry:\n    import aiohttp\n    from aiohttp import web\nexcept ImportError:\n    print(\"Module 'aiohttp' not installed. Please install it via:\")\n    print(\"pip install aiohttp\")\n    print(\"or\")\n    print(\"pip install -r requirements.txt\")\n    sys.exit()\n\nimport mimetypes\nfrom comfy.cli_args import args\n\n\n@web.middleware\nasync def cache_control(request: web.Request, handler):\n    response: web.Response = await handler(request)\n    if request.path.endswith('.js') or request.path.endswith('.css'):\n        response.headers.setdefault('Cache-Control', 'no-cache')\n    return response\n\ndef create_cors_middleware(allowed_origin: str):\n    @web.middleware\n    async def cors_middleware(request: web.Request, handler):\n        if request.method == \"OPTIONS\":\n            # Pre-flight request. Reply successfully:\n            response = web.Response()\n        else:\n            response = await handler(request)\n\n        response.headers['Access-Control-Allow-Origin'] = allowed_origin\n        response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, OPTIONS'\n        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'\n        response.headers['Access-Control-Allow-Credentials'] = 'true'\n        return response\n\n    return cors_middleware\n\nclass PromptServer():\n    def __init__(self, loop):\n        PromptServer.instance = self\n\n        mimetypes.init(); \n        mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'\n        self.prompt_queue = None\n        self.loop = loop\n        self.messages = asyncio.Queue()\n        self.number = 0\n\n        middlewares = [cache_control]\n        if args.cors:\n            middlewares.append(create_cors_middleware(args.cors))\n\n        self.app = web.Application(client_max_size=20971520, middlewares=middlewares)\n        self.sockets = dict()\n        self.web_root = os.path.join(os.path.dirname(\n            os.path.realpath(__file__)), \"web\")\n        routes = web.RouteTableDef()\n        self.routes = routes\n        self.last_node_id = None\n        self.client_id = None\n\n        @routes.get('/ws')\n        async def websocket_handler(request):\n            ws = web.WebSocketResponse()\n            await ws.prepare(request)\n            sid = request.rel_url.query.get('clientId', '')\n            if sid:\n                # Reusing existing session, remove old\n                self.sockets.pop(sid, None)\n            else:\n                sid = uuid.uuid4().hex      \n\n            self.sockets[sid] = ws\n\n            try:\n                # Send initial state to the new client\n                await self.send(\"status\", { \"status\": self.get_queue_info(), 'sid': sid }, sid)\n                # On reconnect if we are the currently executing client send the current node\n                if self.client_id == sid and self.last_node_id is not None:\n                    await self.send(\"executing\", { \"node\": self.last_node_id }, sid)\n                    \n                async for msg in ws:\n                    if msg.type == aiohttp.WSMsgType.ERROR:\n                        print('ws connection closed with exception %s' % ws.exception())\n            finally:\n                self.sockets.pop(sid, None)\n            return ws\n\n        @routes.get(\"/\")\n        async def get_root(request):\n            return web.FileResponse(os.path.join(self.web_root, \"index.html\"))\n\n        @routes.get(\"/embeddings\")\n        def get_embeddings(self):\n            embeddings = folder_paths.get_filename_list(\"embeddings\")\n            return web.json_response(list(map(lambda a: os.path.splitext(a)[0].lower(), embeddings)))\n\n        @routes.get(\"/extensions\")\n        async def get_extensions(request):\n            files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)\n            return web.json_response(list(map(lambda f: \"/\" + os.path.relpath(f, self.web_root).replace(\"\\\\\", \"/\"), files)))\n\n        @routes.post(\"/upload/image\")\n        async def upload_image(request):\n            upload_dir = folder_paths.get_input_directory()\n\n            if not os.path.exists(upload_dir):\n                os.makedirs(upload_dir)\n            \n            post = await request.post()\n            image = post.get(\"image\")\n\n            if image and image.file:\n                filename = image.filename\n                if not filename:\n                    return web.Response(status=400)\n\n                split = os.path.splitext(filename)\n                i = 1\n                while os.path.exists(os.path.join(upload_dir, filename)):\n                    filename = f\"{split[0]} ({i}){split[1]}\"\n                    i += 1\n\n                filepath = os.path.join(upload_dir, filename)\n\n                with open(filepath, \"wb\") as f:\n                    f.write(image.file.read())\n                \n                return web.json_response({\"name\" : filename})\n            else:\n                return web.Response(status=400)\n\n\n        @routes.get(\"/view\")\n        async def view_image(request):\n            if \"filename\" in request.rel_url.query:\n                type = request.rel_url.query.get(\"type\", \"output\")\n                output_dir = folder_paths.get_directory_by_type(type)\n                if output_dir is None:\n                    return web.Response(status=400)\n\n                if \"subfolder\" in request.rel_url.query:\n                    full_output_dir = os.path.join(output_dir, request.rel_url.query[\"subfolder\"])\n                    if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:\n                        return web.Response(status=403)\n                    output_dir = full_output_dir\n\n                filename = request.rel_url.query[\"filename\"]\n                filename = os.path.basename(filename)\n                file = os.path.join(output_dir, filename)\n\n                if os.path.isfile(file):\n                    return web.FileResponse(file, headers={\"Content-Disposition\": f\"filename=\\\"{filename}\\\"\"})\n                \n            return web.Response(status=404)\n\n        @routes.get(\"/prompt\")\n        async def get_prompt(request):\n            return web.json_response(self.get_queue_info())\n\n        @routes.get(\"/object_info\")\n        async def get_object_info(request):\n            out = {}\n            for x in nodes.NODE_CLASS_MAPPINGS:\n                obj_class = nodes.NODE_CLASS_MAPPINGS[x]\n                info = {}\n                info['input'] = obj_class.INPUT_TYPES()\n                info['output'] = obj_class.RETURN_TYPES\n                info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']\n                info['name'] = x #TODO\n                info['description'] = ''\n                info['category'] = 'sd'\n                if hasattr(obj_class, 'CATEGORY'):\n                    info['category'] = obj_class.CATEGORY\n                out[x] = info\n            return web.json_response(out)\n\n        @routes.get(\"/history\")\n        async def get_history(request):\n            return web.json_response(self.prompt_queue.get_history())\n\n        @routes.get(\"/queue\")\n        async def get_queue(request):\n            queue_info = {}\n            current_queue = self.prompt_queue.get_current_queue()\n            queue_info['queue_running'] = current_queue[0]\n            queue_info['queue_pending'] = current_queue[1]\n            return web.json_response(queue_info)\n\n        @routes.post(\"/prompt\")\n        async def post_prompt(request):\n            print(\"got prompt\")\n            resp_code = 200\n            out_string = \"\"\n            json_data =  await request.json()\n\n            if \"number\" in json_data:\n                number = float(json_data['number'])\n            else:\n                number = self.number\n                if \"front\" in json_data:\n                    if json_data['front']:\n                        number = -number\n\n                self.number += 1\n\n            if \"prompt\" in json_data:\n                prompt = json_data[\"prompt\"]\n                valid = execution.validate_prompt(prompt)\n                extra_data = {}\n                if \"extra_data\" in json_data:\n                    extra_data = json_data[\"extra_data\"]\n\n                if \"client_id\" in json_data:\n                    extra_data[\"client_id\"] = json_data[\"client_id\"]\n                if valid[0]:\n                    self.prompt_queue.put((number, id(prompt), prompt, extra_data))\n                else:\n                    resp_code = 400\n                    out_string = valid[1]\n                    print(\"invalid prompt:\", valid[1])\n\n            return web.Response(body=out_string, status=resp_code)\n        \n        @routes.post(\"/queue\")\n        async def post_queue(request):\n            json_data =  await request.json()\n            if \"clear\" in json_data:\n                if json_data[\"clear\"]:\n                    self.prompt_queue.wipe_queue()\n            if \"delete\" in json_data:\n                to_delete = json_data['delete']\n                for id_to_delete in to_delete:\n                    delete_func = lambda a: a[1] == int(id_to_delete)\n                    self.prompt_queue.delete_queue_item(delete_func)\n                    \n            return web.Response(status=200)\n\n        @routes.post(\"/interrupt\")\n        async def post_interrupt(request):\n            nodes.interrupt_processing()\n            return web.Response(status=200)\n\n        @routes.post(\"/history\")\n        async def post_history(request):\n            json_data =  await request.json()\n            if \"clear\" in json_data:\n                if json_data[\"clear\"]:\n                    self.prompt_queue.wipe_history()\n            if \"delete\" in json_data:\n                to_delete = json_data['delete']\n                for id_to_delete in to_delete:\n                    self.prompt_queue.delete_history_item(id_to_delete)\n\n            return web.Response(status=200)\n        \n    def add_routes(self):\n        self.app.add_routes(self.routes)\n        self.app.add_routes([\n            web.static('/', self.web_root),\n        ])\n\n    def get_queue_info(self):\n        prompt_info = {}\n        exec_info = {}\n        exec_info['queue_remaining'] = self.prompt_queue.get_tasks_remaining()\n        prompt_info['exec_info'] = exec_info\n        return prompt_info\n\n    async def send(self, event, data, sid=None):\n        message = {\"type\": event, \"data\": data}\n       \n        if isinstance(message, str) == False:\n            message = json.dumps(message)\n\n        if sid is None:\n            for ws in self.sockets.values():\n                await ws.send_str(message)\n        elif sid in self.sockets:\n            await self.sockets[sid].send_str(message)\n\n    def send_sync(self, event, data, sid=None):\n        self.loop.call_soon_threadsafe(\n            self.messages.put_nowait, (event, data, sid))\n\n    def queue_updated(self):\n        self.send_sync(\"status\", { \"status\": self.get_queue_info() })\n\n    async def publish_loop(self):\n        while True:\n            msg = await self.messages.get()\n            await self.send(*msg)\n\n    async def start(self, address, port, verbose=True, call_on_start=None):\n        runner = web.AppRunner(self.app)\n        await runner.setup()\n        site = web.TCPSite(runner, address, port)\n        await site.start()\n\n        if address == '':\n            address = '0.0.0.0'\n        if verbose:\n            print(\"Starting server\\n\")\n            print(\"To see the GUI go to: http://{}:{}\".format(address, port))\n        if call_on_start is not None:\n            call_on_start(address, port)\n\n", "path": "server.py"}, {"content": "import argparse\n\nparser = argparse.ArgumentParser()\n\nparser.add_argument(\"--listen\", nargs=\"?\", const=\"0.0.0.0\", default=\"127.0.0.1\", type=str, help=\"Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)\")\nparser.add_argument(\"--port\", type=int, default=8188, help=\"Set the listen port.\")\nparser.add_argument(\"--cors\", default=None, nargs=\"?\", const=\"*\", help=\"Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.\")\nparser.add_argument(\"--extra-model-paths-config\", type=str, default=None, help=\"Load an extra_model_paths.yaml file.\")\nparser.add_argument(\"--output-directory\", type=str, default=None, help=\"Set the ComfyUI output directory.\")\nparser.add_argument(\"--cuda-device\", type=int, default=None, help=\"Set the id of the cuda device this instance will use.\")\nparser.add_argument(\"--dont-upcast-attention\", action=\"store_true\", help=\"Disable upcasting of attention. Can boost speed but increase the chances of black images.\")\n\nattn_group = parser.add_mutually_exclusive_group()\nattn_group.add_argument(\"--use-split-cross-attention\", action=\"store_true\", help=\"Use the split cross attention optimization instead of the sub-quadratic one. Ignored when xformers is used.\")\nattn_group.add_argument(\"--use-pytorch-cross-attention\", action=\"store_true\", help=\"Use the new pytorch 2.0 cross attention function.\")\n\nparser.add_argument(\"--disable-xformers\", action=\"store_true\", help=\"Disable xformers.\")\n\nvram_group = parser.add_mutually_exclusive_group()\nvram_group.add_argument(\"--highvram\", action=\"store_true\", help=\"By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory.\")\nvram_group.add_argument(\"--normalvram\", action=\"store_true\", help=\"Used to force normal vram use if lowvram gets automatically enabled.\")\nvram_group.add_argument(\"--lowvram\", action=\"store_true\", help=\"Split the unet in parts to use less vram.\")\nvram_group.add_argument(\"--novram\", action=\"store_true\", help=\"When lowvram isn't enough.\")\nvram_group.add_argument(\"--cpu\", action=\"store_true\", help=\"To use the CPU for everything (slow).\")\n\nparser.add_argument(\"--dont-print-server\", action=\"store_true\", help=\"Don't print server output.\")\nparser.add_argument(\"--quick-test-for-ci\", action=\"store_true\", help=\"Quick test for CI.\")\nparser.add_argument(\"--windows-standalone-build\", action=\"store_true\", help=\"Windows standalone build.\")\n\nargs = parser.parse_args()\n", "path": "comfy/cli_args.py"}]}