Skip to content

Instantly share code, notes, and snippets.

@Vizonex
Last active October 19, 2025 19:41
Show Gist options
  • Select an option

  • Save Vizonex/ae69ce64a7ef7ec8683afeffc90ba554 to your computer and use it in GitHub Desktop.

Select an option

Save Vizonex/ae69ce64a7ef7ec8683afeffc90ba554 to your computer and use it in GitHub Desktop.
concet for a new writer for aiohttp based written in CPython based off the current aiohttp _http_writer written in cython but with a few more apis for http/2 http/3 frame packing
/* This CPython Writer is a mirror of the aiohttp & Yarl source code for implementing
a fast and easy to use writer that could be implemented & Used in Cython and
CPython projects. This was an idea for the transition of the current _http_writer
cython module in aiohttp on over to C When or if we decide to go down that route with
header serlization with Http/1.1 ,
This also has new tools that could be used for http/2 Frame writing
so this library also implements tools that the struct module would use but for writing
in special bytes.
This code is under than MIT/Appache 2.0 LICENSE
Author: Vizonex
*/
#ifndef __CWRITER_H__
#define __CWRITER_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <Python.h>
#include <stdint.h> /* uint64_t */
#include <string.h> /* memset */
/* TODO: A Macro for altering writer's typename if nessary by an end developer
incase this name comes off as being too generic */
typedef struct writer_s writer_t;
struct writer_s {
char* buf;
Py_ssize_t size;
Py_ssize_t pos;
Py_ssize_t realloc_size;
/* TODO: Store a flag in this object instead of a heap_allocated boolean
* as code-safety */
int heap_allocated;
};
static inline void
writer_init(writer_t* writer, char* buf, Py_ssize_t size){
writer->buf = buf;
writer->size = size;
/* will use this size to increment the heap by */
writer->realloc_size = size;
writer->pos = 0;
writer->heap_allocated = 0;
}
static inline void
writer_release(writer_t* writer){
if (writer->heap_allocated){
PyMem_Free(writer->buf);
}
}
static inline int
writer_add_byte(writer_t* writer, uint8_t ch){
char* buf;
Py_ssize_t size;
if (writer->pos == writer->size){
/* Reallocate Memory */
size = writer->size + writer->realloc_size;
if (!writer->heap_allocated){
buf = (char*)PyMem_Malloc(size);
if (buf == NULL){
PyErr_NoMemory();
return -1;
}
memcpy(buf , writer->buf, writer->size);
/* set flag that were heap allocated */
writer->heap_allocated = 1;
} else {
buf = (char*)PyMem_Realloc(writer->buf, size);
if (buf == NULL){
PyErr_NoMemory();
return -1;
}
}
writer->buf = buf;
writer->size = size;
}
writer->buf[writer->pos] = (char)(ch);
writer->pos++;
return 0;
}
static inline int
writer_add_utf8(writer_t* writer, Py_UCS4 symbol){
uint64_t utf = (uint64_t)(symbol);
if (utf < 0x80){
return writer_add_byte(writer, (uint8_t)(utf));
}
if (utf < 0x800){
if (writer_add_byte(writer, (uint8_t)(0xc0 | (utf >> 6))) < 0)
return -1;
return writer_add_byte(writer, (uint8_t)(0x80 | (utf & 0x3f)));
}
if (0xD800 <= utf <= 0xDFFF){
/* Ignore Surrogate Escape Values */
return 0;
}
if (utf < 0x10000){
if (writer_add_byte(writer, (uint8_t)(0xe0 | (utf >> 12))) < 0)
return -1;
if (writer_add_byte(writer, (uint8_t)(0x80 | ((utf >> 6) & 0x3f))) < 0)
return -1;
return writer_add_byte(writer, (uint8_t)(0x80 | (utf & 0x3f)));
}
if (utf > 0x10FFFF){
/* symbol is too large*/
return 0;
}
if (writer_add_byte(writer, (uint8_t)(0xf0 | (utf >> 18))) < 0)
return -1;
if (writer_add_byte(writer,
(uint8_t)(0x80 | ((utf >> 12) & 0x3f))) < 0)
return -1;
if (writer_add_byte(writer,
(uint8_t)(0x80 | ((utf >> 6) & 0x3f))) < 0)
return -1;
return writer_add_byte(writer, (uint8_t)(0x80 | (utf & 0x3f)));
}
static inline int
writer_add_unicode(writer_t* writer, PyObject* s){
int ret = -1;
Py_INCREF(s);
Py_UCS4* chars = PyUnicode_4BYTE_DATA(s);
Py_ssize_t size = PyUnicode_GET_LENGTH(s);
for (Py_ssize_t i = 0; i < size; i++){
if (writer_add_utf8(writer, chars[i]) < 0){
goto exit;
}
}
ret = 0;
exit:
Py_DECREF(s);
return ret;
}
/* This point onwards is custom and does not come directly from aiohttp or yarl
and is licensed under MIT */
static int writer_add_buffer(writer_t* writer, PyObject* buffer){
Py_buffer buf;
int ret = -1;
/* Ensure this buffer is held onto until were out of scope of this function */
Py_INCREF(buffer);
if (PyObject_GetBuffer(buffer, &buf, PyBUF_SIMPLE) < 0){
goto exit;
}
char* data = (char*)(buf.buf);
for (Py_ssize_t i = 0; i < buf.len; i++){
if (writer_add_byte(writer, data[i]) < 0){
PyBuffer_Release(&buf);
goto exit;
}
}
PyBuffer_Release(&buf);
ret = 0;
exit:
Py_DECREF(buffer);
return ret;
}
/* Packs a uint16 value in little-endian order */
static int writer_pack_u16(writer_t* writer, const uint16_t v){
uint16_t x = v;
if (writer_add_byte(writer, (uint8_t)(x & 0xFF)) < 0){
return -1;
}
x >>= 8;
return writer_add_byte(writer, (uint8_t)(x & 0xFF));
}
#define CWRITER_PACK_BY(WRITER, BITCOUNT, X) \
for (size_t i = 0; i < BITCOUNT; i++){\
if (writer_add_byte(WRITER, (uint8_t)(X & 0xFF)) < 0){\
return -1; \
}\
X <<= 8;\
}
/* They are the same thing... */
// static inline int writer_pack_u8(writer_t* writer, const uint8_t v);
#define writer_pack_u8 writer_add_byte
/* Packs a uint32_t as uint24_t in little endian order */
static inline int
writer_pack_u24(writer_t* writer, const uint32_t v){
uint32_t x = v;
CWRITER_PACK_BY(writer, 3, x);
return 0;
}
/* Packs a uint32_t in little endian order */
static inline int
writer_pack_u32(writer_t* writer, const uint32_t v){
uint32_t x = v;
CWRITER_PACK_BY(writer, 4, x);
return 0;
}
/* Packs a uint64_t in little endian order */
static inline int
writer_pack_u64(writer_t* writer, const uint64_t v){
uint32_t x = v;
CWRITER_PACK_BY(writer, 4, x);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif // __CWRITER_H__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment