Repository: reflex-dev/reflex
Pull request: expose socket constants
fixes #3081

Problem statement:
Entering more than 999775 characters breaks text_area
**Describe the bug**
Entering more than 999775 characters breaks rx.text_area. If you enter more than 999775 characters into a text area you will have to refresh the page for the text_area to work again.

**To Reproduce**
Steps to reproduce the behavior:

```
import reflex as rx
import pyperclip

class State(rx.State):

    def print_length(self, value: str):
        print(len(value))

    def copy_to_clipboard(text_length):
        # Copy 'text_length' number of 'a's to the clipboard
        text_length = 999776    # Fails
        # text_length = 999775  # Works
        text = 'a' * text_length
        pyperclip.copy(text)
        print(f"Copied {text_length} characters to the clipboard.")

def index() -> rx.Component:
    return rx.center(
        rx.text_area(
            on_change=State.print_length,
            height="50vh",
            width="50vw",
        ),
        height="100vh",
    )

app = rx.App()
app.add_page(index, on_load=State.copy_to_clipboard)
```

**Expected behavior**
The text area should behave the same way that it does when you enter less than 999776 characters.

**Specifics (please complete the following information):**
 - Python Version: 3.11.6
 - Reflex Version: 0.4.7
 - OS: Ubuntu 23.10
 - Browser (Optional): Chrome & Firefox

Hints from issue discussion:
Hi! 

This error happens because the size of a message sent from browser to backend  by default is limited to ~1Mb, you can see this limitation [here](https://github.com/reflex-dev/reflex/blob/0a8aaea599f95bb6c4730c743f6c4b636134ca6a/reflex/app.py#L234) in code. In `reflex.App` you can pass your custom socket engine in `sio` argument and socket app in `socket_app` argument. However, I found a bug that these args are not used.  In PR #3170 I suggest a fix to this problem.

If it will be merged, you can pass any max buffer size to sio and your code will work as expected.
Example:

```
import reflex as rx
from socketio import AsyncServer

import pyperclip


class State(rx.State):

    def print_length(self, value: str):
        print(len(value))

    def copy_to_clipboard(self):
        # Copy 'text_length' number of 'a's to the clipboard
        text_length = 999776   # Works
        text = 'a' * text_length
        pyperclip.copy(text)
        print(f"Copied {text_length} characters to the clipboard.")


def index() -> rx.Component:
    return rx.center(
        rx.text_area(
            on_change=State.print_length,
            height="50vh",
            width="50vw",
        ),
        height="100vh",
    )

sio=AsyncServer(
            async_mode="asgi",
            cors_allowed_origins=(
                "*"
            ),
            cors_credentials=True,
            max_http_buffer_size=50000000000,
            ping_interval=120,
            ping_timeout=240)

app = rx.App(sio=sio)
app.add_page(index, on_load=State.copy_to_clipboard)
```

You can find `socketio` docs for maximum buffer size `max_http_buffer_size` [here](https://python-socketio.readthedocs.io/en/stable/api.html)
`Reflex` docs for `sio` and `socket_app` kwargs [here](https://reflex.dev/docs/api-reference/app/)
@Snaipergelka 's code above now triggers an error in Reflex 0.7.0:

```
Task exception was never retrieved
future: <Task finished name='Task-10' coro=<AsyncServer._handle_event_internal() done, defined at /home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_server.py:610> exception=TypeError('Object of type StateUpdate is not JSON serializable')>
Traceback (most recent call last):
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_server.py", line 612, in _handle_event_internal
    r = await server._trigger_event(data[0], namespace, sid, *data[1:])
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_server.py", line 662, in _trigger_event
    return await handler.trigger_event(event, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_namespace.py", line 38, in trigger_event
    ret = await handler(*args)
          ^^^^^^^^^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/reflex/app.py", line 1754, in on_event
    await self.emit_update(update=update, sid=sid)
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/reflex/app.py", line 1685, in emit_update
    await asyncio.create_task(
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_namespace.py", line 70, in emit
    return await self.server.emit(event, data=data, to=to, room=room,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_server.py", line 178, in emit
    await self.manager.emit(event, data, namespace, room=room,
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/async_manager.py", line 39, in emit
    encoded_packet = pkt.encode()
                     ^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/socketio/packet.py", line 64, in encode
    encoded_packet += self.json.dumps(data, separators=(',', ':'))
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
          ^^^^^^^^^^^
  File "/usr/lib/python3.12/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/home/user/work-computers-sync/code/reflex-bugs/sio/venv/lib/python3.12/site-packages/reflex/state.py", line 4008, in _json_encoder_default_wrapper
    return _orig_json_encoder_default(self, o)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type StateUpdate is not JSON serializable
```
Is there another way that I can change the value of max_http_buffer_size until this gets fixed?
@jq6l43d1 pass `json` argument

```python
import json
from types import SimpleNamespace
from socketio import AsyncServer
from reflex.utils import format
import reflex as rx

sio=AsyncServer(
            async_mode="asgi",
            cors_allowed_origins=(
                "*"
            ),
            cors_credentials=True,
            max_http_buffer_size=50000000000,
            ping_interval=120,
            ping_timeout=240,
            json=SimpleNamespace(
                dumps=staticmethod(format.json_dumps),
                loads=staticmethod(json.loads),
            ),
)
app = rx.App(sio=sio)
```

Need to find a better way to expose this
@masenf do we want an env flag for max_http_buffer_size?
Possibly. It's also a thing that can be set at runtime...

```python
class State(rx.State):
    """The app state."""
    max_http_buffer_size: int = 0
    txt: str = ""

    def on_load(self):
        self.max_http_buffer_size = app.sio.eio.max_http_buffer_size

    def set_max_http_buffer_size(self, v: str):
        self.max_http_buffer_size = int(v)
        app.sio.eio.max_http_buffer_size = self.max_http_buffer_size
```

But it only works if you set it after compile.

Probably makes best sense to expose the following "constants" as environment vars.

```python
                max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
                ping_interval=constants.Ping.INTERVAL,
                ping_timeout=constants.Ping.TIMEOUT,
```

Commit message: expose socket constants

Patch to review:
diff --git a/reflex/app.py b/reflex/app.py
index 2c823ec1e4b..3c05b1b1496 100644
--- a/reflex/app.py
+++ b/reflex/app.py
@@ -498,9 +498,9 @@ def _setup_state(self) -> None:
                     else config.cors_allowed_origins
                 ),
                 cors_credentials=True,
-                max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
-                ping_interval=constants.Ping.INTERVAL,
-                ping_timeout=constants.Ping.TIMEOUT,
+                max_http_buffer_size=environment.REFLEX_SOCKET_POLLING_MAX_HTTP_BUFFER_SIZE.get(),
+                ping_interval=environment.REFLEX_SOCKET_INTERVAL.get(),
+                ping_timeout=environment.REFLEX_SOCKET_TIMEOUT.get(),
                 json=SimpleNamespace(
                     dumps=staticmethod(format.json_dumps),
                     loads=staticmethod(json.loads),
diff --git a/reflex/config.py b/reflex/config.py
index f3c64178982..6fc1fc397e8 100644
--- a/reflex/config.py
+++ b/reflex/config.py
@@ -723,6 +723,17 @@ class EnvironmentVariables:
     # The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
     REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
 
+    # Maximum size of the message in the websocket server in bytes.
+    REFLEX_SOCKET_POLLING_MAX_HTTP_BUFFER_SIZE: EnvVar[int] = env_var(
+        constants.POLLING_MAX_HTTP_BUFFER_SIZE
+    )
+
+    # The interval to send a ping to the websocket server in seconds.
+    REFLEX_SOCKET_INTERVAL: EnvVar[int] = env_var(constants.Ping.INTERVAL)
+
+    # The timeout to wait for a pong from the websocket server in seconds.
+    REFLEX_SOCKET_TIMEOUT: EnvVar[int] = env_var(constants.Ping.TIMEOUT)
+
 
 environment = EnvironmentVariables()
 


Code review comments:
diff_hunk: @@ -498,9 +498,9 @@ def _setup_state(self) -> None:
                     else config.cors_allowed_origins
                 ),
                 cors_credentials=True,
-                max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
-                ping_interval=constants.Ping.INTERVAL,
-                ping_timeout=constants.Ping.TIMEOUT,
+                max_http_buffer_size=environment.REFLEX_SOCKET_POLLING_MAX_HTTP_BUFFER_SIZE.get(),
path: reflex/app.py
text: @user1:
suggest removing `_POLLING` from the name as this also applies to the websocket max message size