Hi,

While playing around with the server-transform plugin example, i noticed it
was broken. After some meddling around, i got it working again.
The changes are mainly that in some cases content-length was passed in
network-byte order where it shouldn't be.
Another fix was handling the TS_EVENT_IMMEDIATE event
in transform_write_event(). i currently perform
a TSVIOReenable(data->server_vio); on that event, and it seems to work.
However, I'm not sure at all if that is the correct thing to do, so if
anybody could help me out on that? :-).

I have tested the changed code with a custom echo server, and confirmed that
the plugin is actually sending and receiving to the echo server.
I added the working code below.

Regards,
Otto van der Schaaf


#include <string.h>
#include <stdio.h>

#if !defined (_WIN32)
#  include <netinet/in.h>
#else
#  include <windows.h>
#endif

#include <ts/ts.h>

#define STATE_BUFFER       1
#define STATE_CONNECT      2
#define STATE_WRITE        3
#define STATE_READ_STATUS  4
#define STATE_READ         5
#define STATE_BYPASS       6

typedef struct
{
  int state;
  TSHttpTxn txn;

  TSIOBuffer input_buf;
  TSIOBufferReader input_reader;

  TSIOBuffer output_buf;
  TSIOBufferReader output_reader;
  TSVConn output_vc;
  TSVIO output_vio;

  TSAction pending_action;
  TSVConn server_vc;
  TSVIO server_vio;

  int content_length;
} TransformData;

static TSCont transform_create(TSHttpTxn txnp);
static void transform_destroy(TSCont contp);
static int transform_connect(TSCont contp, TransformData * data);
static int transform_write(TSCont contp, TransformData * data);
static int transform_read_status(TSCont contp, TransformData * data);
static int transform_read(TSCont contp, TransformData * data);
static int transform_bypass(TSCont contp, TransformData * data);
static int transform_buffer_event(TSCont contp, TransformData * data,
TSEvent event, void *edata);
static int transform_connect_event(TSCont contp, TransformData * data,
TSEvent event, void *edata);
static int transform_write_event(TSCont contp, TransformData * data, TSEvent
event, void *edata);
static int transform_read_status_event(TSCont contp, TransformData * data,
TSEvent event, void *edata);
static int transform_read_event(TSCont contp, TransformData * data, TSEvent
event, void *edata);
static int transform_bypass_event(TSCont contp, TransformData * data,
TSEvent event, void *edata);
static int transform_handler(TSCont contp, TSEvent event, void *edata);

#if !defined (_WIN32)
static in_addr_t server_ip;
#else
static unsigned int server_ip;
#endif

static int server_port;

static TSCont transform_create(TSHttpTxn txnp)
{
  TSCont contp;
  TransformData *data;

  contp = TSTransformCreate(transform_handler, txnp);

  data = (TransformData *) TSmalloc(sizeof(TransformData));
  data->state = STATE_BUFFER;
  data->txn = txnp;
  data->input_buf = NULL;
  data->input_reader = NULL;
  data->output_buf = NULL;
  data->output_reader = NULL;
  data->output_vio = NULL;
  data->output_vc = NULL;
  data->pending_action = NULL;
  data->server_vc = NULL;
  data->server_vio = NULL;
  data->content_length = 0;

  TSContDataSet(contp, data);
  return contp;
}

static void transform_destroy(TSCont contp)
{
  TransformData *data;

  data = TSContDataGet(contp);
  if (data != NULL) {
    if (data->input_buf)
      TSIOBufferDestroy(data->input_buf);

    if (data->output_buf)
      TSIOBufferDestroy(data->output_buf);

    if (data->pending_action)
      TSActionCancel(data->pending_action);

    if (data->server_vc)
      TSVConnAbort(data->server_vc, 1);

    TSfree(data);
  } else {
    TSError("Unable to get Continuation's Data. TSContDataGet returns
NULL");
  }

  TSContDestroy(contp);
}

static int transform_connect(TSCont contp, TransformData * data)
{
  TSDebug ("strans", "@@@ transform_connect\n");
  TSAction action;
  int content_length;
  struct sockaddr_in ip_addr;

  data->state = STATE_CONNECT;

  content_length = TSIOBufferReaderAvail(data->input_reader);

  TSDebug ("strans", "@@@ transform_connect: content length
%i\n",content_length);

  if (content_length >= 0) {
    data->content_length = content_length;
    data->content_length = htonl(data->content_length);
    TSDebug ("strans", "@@@ transform_connect: htonl / data->content_length
%i\n",data->content_length);
    /* Prepend the content length to the buffer.
     * If we decide to not send the content to the transforming
     * server then we need to make sure and skip input_reader
     * over the content length.
     */

    {
      TSIOBuffer temp;
      TSIOBufferReader tempReader;

      temp = TSIOBufferCreate();
      tempReader = TSIOBufferReaderAlloc(temp);

      TSDebug ("strans", "@@@ tmp reader created and allocated\n");

      int TSIOBufferWriteRes = TSIOBufferWrite(temp, (const char *)
&content_length, sizeof(int));
      TSDebug ("strans", "@@@ TSIOBufferWrite / wrote %i bytes for content
length\n",TSIOBufferWriteRes);
      TSDebug ("strans", "@@@ TSIOBufferWrite / wrote content length\n");


      int TSIOBufferCopyres = TSIOBufferCopy(temp, data->input_reader,
content_length, 0);
      TSDebug ("strans", "@@@ TSIOBufferWrite / wrote %i
bytes\n",TSIOBufferCopyres);
      TSDebug ("strans", "@@@ TSIOBufferCopy executed\n");

      TSIOBufferReaderFree(data->input_reader);
      TSIOBufferDestroy(data->input_buf);
      data->input_buf = temp;
      data->input_reader = tempReader;
    }
  } else {
    TSError("TSIOBufferReaderAvail returns TS_ERROR");
    return 0;
  }

  /* TODO: This only supports IPv4, probably should be changed at some
point, but
     it's an example ... */
  memset(&ip_addr, 0, sizeof(ip_addr));
  ip_addr.sin_family = AF_INET;
  ip_addr.sin_addr.s_addr = server_ip; /* Should be in network byte order */
  ip_addr.sin_port = server_port;
  action = TSNetConnect(contp, (struct sockaddr const*)&ip_addr);

  if (!TSActionDone(action)) {
    data->pending_action = action;
  }

  return 0;
}

static int transform_write(TSCont contp, TransformData * data)
{
  TSDebug ("strans", "@@@ transform_write\n");
  int content_length;

  data->state = STATE_WRITE;

  content_length = TSIOBufferReaderAvail(data->input_reader);
  if (content_length >= 0) {
    TSDebug ("strans", "@@@ transform_write - %i bytes\n", content_length);
    data->server_vio = TSVConnWrite(data->server_vc, contp,
TSIOBufferReaderClone(data->input_reader), content_length);
    TSDebug ("strans", "@@@ transform_write - data_server_vio: %i \n",
data->server_vio);
  } else {
    TSError("TSIOBufferReaderAvail returns TS_ERROR");
  }
  return 0;
}

static int transform_read_status(TSCont contp, TransformData * data)
{
  TSDebug ("strans", "@@@ transform_read_status\n");
  data->state = STATE_READ_STATUS;

  data->output_buf = TSIOBufferCreate();
  data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
  if (data->output_reader != NULL) {
    data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf,
sizeof(int));
  } else {
    TSError("Error in Allocating a Reader to output buffer.
TSIOBufferReaderAlloc returns NULL");
  }

  return 0;
}

static int transform_read(TSCont contp, TransformData * data)
{
  TSDebug ("strans", "@@@ transform_read\n");
  data->state = STATE_READ;

  TSIOBufferDestroy(data->input_buf);
  data->input_buf = NULL;
  data->input_reader = NULL;

  data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf,
data->content_length);
  data->output_vc = TSTransformOutputVConnGet((TSVConn) contp);
  if (data->output_vc == NULL) {
    TSError("TSTransformOutputVConnGet returns NULL");
  } else {
    data->output_vio = TSVConnWrite(data->output_vc, contp,
data->output_reader, data->content_length);
    if (data->output_vio == NULL) {
      TSError("TSVConnWrite returns NULL");
    }
  }

  return 0;
}

static int transform_bypass(TSCont contp, TransformData * data)
{
  TSDebug ("strans", "@@@ transform_bypass\n");
  data->state = STATE_BYPASS;

  if (data->server_vc) {
    TSVConnAbort(data->server_vc, 1);
    data->server_vc = NULL;
    data->server_vio = NULL;
  }

  if (data->output_buf) {
    TSIOBufferDestroy(data->output_buf);
    data->output_buf = NULL;
    data->output_reader = NULL;
  }

  TSIOBufferReaderConsume(data->input_reader, sizeof(int));
  data->output_vc = TSTransformOutputVConnGet((TSVConn) contp);
  if (data->output_vc == NULL) {
    TSError("TSTransformOutputVConnGet returns NULL");
  } else {
    data->output_vio =
      TSVConnWrite(data->output_vc, contp, data->input_reader,
TSIOBufferReaderAvail(data->input_reader));
    if (data->output_vio == NULL) {
      TSError("TSVConnWrite returns NULL");
    }
  }
  return 1;
}

static int transform_buffer_event(TSCont contp, TransformData * data,
TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_buffer_event\n");
  TSVIO write_vio;
  int towrite;
  int avail;

  if (!data->input_buf) {
    data->input_buf = TSIOBufferCreate();
    data->input_reader = TSIOBufferReaderAlloc(data->input_buf);
  }

  /* Get the write VIO for the write operation that was performed on
     ourself. This VIO contains the buffer that we are to read from
     as well as the continuation we are to call when the buffer is
     empty. */
  write_vio = TSVConnWriteVIOGet(contp);

  /* We also check to see if the write VIO's buffer is non-NULL. A
     NULL buffer indicates that the write operation has been
     shutdown and that the continuation does not want us to send any
     more WRITE_READY or WRITE_COMPLETE events. For this buffered
     transformation that means we're done buffering data. */
  if (!TSVIOBufferGet(write_vio)) {
    return transform_connect(contp, data);
  }

  /* Determine how much data we have left to read. For this server
     transform plugin this is also the amount of data we have left
     to write to the output connection. */
  towrite = TSVIONTodoGet(write_vio);
  if (towrite > 0) {
    /* The amount of data left to read needs to be truncated by
       the amount of data actually in the read buffer. */
    avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
    if (towrite > avail) {
      towrite = avail;
    }

    if (towrite > 0) {
      /* Copy the data from the read buffer to the input buffer. */
      TSIOBufferCopy(data->input_buf, TSVIOReaderGet(write_vio), towrite,
0);

      /* Tell the read buffer that we have read the data and are no
         longer interested in it. */
      TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);

      /* Modify the write VIO to reflect how much data we've
         completed. */
      TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
    }
  }

  /* Now we check the write VIO to see if there is data left to
     read. */
  if (TSVIONTodoGet(write_vio) > 0) {
    /* Call back the write VIO continuation to let it know that we
       are ready for more data. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY,
write_vio);
  } else {
    /* Call back the write VIO continuation to let it know that we
       have completed the write operation. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE,
write_vio);

    /* start compression... */
    return transform_connect(contp, data);
  }

  return 0;
}

static int transform_connect_event(TSCont contp, TransformData * data,
TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_connect_event\n");
  switch (event) {
  case TS_EVENT_NET_CONNECT:
    TSDebug ("strans", "@@@ transform_connect_event -
TS_EVENT_NET_CONNECT\n");
    data->pending_action = NULL;
    data->server_vc = (TSVConn) edata;
    return transform_write(contp, data);
  case TS_EVENT_NET_CONNECT_FAILED:
    TSDebug ("strans", "@@@ transform_connect_event -
TS_EVENT_NET_CONNECT_FAILED\n");
    data->pending_action = NULL;
    return transform_bypass(contp, data);
  default:
    TSDebug ("strans", "@@@ transform_connect_event - default\n");
    break;
  }

  return 0;
}

static int transform_write_event(TSCont contp, TransformData * data, TSEvent
event, void *edata)
{
    TSDebug ("strans", "@@@ transform_write_event - WAAH2\n");
  switch (event) {
  case TS_EVENT_VCONN_WRITE_READY:
    TSDebug ("strans", "@@@ transform_write_event -
S_EVENT_VCONN_WRITE_READY\n");
    TSVIOReenable(data->server_vio);
    break;
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSDebug ("strans", "@@@ transform_write_event -
TS_EVENT_VCONN_WRITE_COMPLETE\n");
    return transform_read_status(contp, data);
  case TS_EVENT_ERROR:
    TSDebug ("strans", "@@@ transform_write_event - Explicit
transform_write_event error. Close down and bypass\n");
    return transform_bypass(contp, data);
  case TS_EVENT_IMMEDIATE:
    TSDebug ("strans", "@@@ transform_write_event - IMMEDIATE\n");
    TSVIOReenable(data->server_vio);
    break;
  default:
    /* An error occurred while writing to the server. Close down the
connection to the server and bypass. */
    TSDebug ("strans", "@@@ transform_write_event - An error occurred while
writing to the server:%d. Close down/bypass\n",event);
    return transform_bypass(contp, data);
  }

  return 0;
}

static int transform_read_status_event(TSCont contp, TransformData * data,
TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_read_status_event\n");
  switch (event) {
  case TS_EVENT_ERROR:
  case TS_EVENT_VCONN_EOS:
    TSDebug ("strans", "@@@ transform_read_status_event / TS_EVENT_ERROR |
TS_EVENT_VCONN_EOS\n");
    return transform_bypass(contp, data);
  case TS_EVENT_VCONN_READ_COMPLETE:
    TSDebug ("strans", "@@@ transform_read_status_event /
TS_EVENT_VCONN_READ_COMPLETE\n");
    if (TSIOBufferReaderAvail(data->output_reader) == sizeof(int)) {
      TSIOBufferBlock blk;
      char *buf;
      void *buf_ptr;
      int64_t avail;
      int64_t read_nbytes = sizeof(int);
      int64_t read_ndone = 0;

      TSDebug ("strans", "@@@ transform_read_status_event %d
bytes\n",&data->content_length);

      buf_ptr = &data->content_length;
      while (read_nbytes > 0) {
        TSDebug ("strans", "@@@ transform_read_status_event read_nbytes:
%d\n",read_nbytes);
        blk = TSIOBufferReaderStart(data->output_reader);
        buf = (char *) TSIOBufferBlockReadStart(blk, data->output_reader,
&avail);
        read_ndone = (avail >= read_nbytes) ? read_nbytes : avail;
        memcpy(buf_ptr, buf, read_ndone);
        if (read_ndone > 0) {
          TSIOBufferReaderConsume(data->output_reader, read_ndone);
          read_nbytes -= read_ndone;
          /* move ptr frwd by read_ndone bytes */
          buf_ptr = (char *) buf_ptr + read_ndone;
        }
      }
      //data->content_length = ntohl(data->content_length);
      //data->content_length = ntohl(data->content_length);
      TSDebug ("strans", "@@@ transform_read_status_event
data->content_length: %d\n", data->content_length);
      return transform_read(contp, data);
    }
    TSDebug ("strans", "@@@ transform_read_status_event /
TS_EVENT_VCONN_READ_COMPLETE -> return bypass?\n");
    return transform_bypass(contp, data);
  default:
    break;
  }

  return 0;
}

static int transform_read_event(TSCont contp, TransformData * data, TSEvent
event, void *edata)
{
  TSDebug ("strans", "@@@ transform_read_event\n");
  switch (event) {
  case TS_EVENT_ERROR:
    TSDebug ("strans", "@@@ transform_read_event / TS_EVENT_ERROR\n");
    TSVConnAbort(data->server_vc, 1);
    data->server_vc = NULL;
    data->server_vio = NULL;

    TSVConnAbort(data->output_vc, 1);
    data->output_vc = NULL;
    data->output_vio = NULL;
    break;
  case TS_EVENT_VCONN_EOS:
    TSDebug ("strans", "@@@ transform_read_event / TS_EVENT_VCONN_EOS\n");
    TSVConnAbort(data->server_vc, 1);
    data->server_vc = NULL;
    data->server_vio = NULL;

    TSVConnAbort(data->output_vc, 1);
    data->output_vc = NULL;
    data->output_vio = NULL;
    break;
  case TS_EVENT_VCONN_READ_COMPLETE:
    TSDebug ("strans", "@@@ transform_read_event /
TS_EVENT_VCONN_READ_COMPLETE\n");
    TSVConnClose(data->server_vc);
    data->server_vc = NULL;
    data->server_vio = NULL;

    TSVIOReenable(data->output_vio);
    break;
  case TS_EVENT_VCONN_READ_READY:
    TSDebug ("strans", "@@@ transform_read_event /
TS_EVENT_VCONN_READ_READY\n");
    TSVIOReenable(data->output_vio);
    break;
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSDebug ("strans", "@@@ transform_read_event /
TS_EVENT_VCONN_WRITE_COMPLETE\n");
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  case TS_EVENT_VCONN_WRITE_READY:
    TSDebug ("strans", "@@@ transform_read_event /
TS_EVENT_VCONN_WRITE_READY\n");
    TSVIOReenable(data->server_vio);
    break;
  default:
    break;
  }

  return 0;
}

static int transform_bypass_event(TSCont contp, TransformData * data,
TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_bypass_event\n");
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSDebug ("strans", "@@@ transform_bypass_event /
TS_EVENT_VCONN_WRITE_COMPLETE\n");
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  case TS_EVENT_VCONN_WRITE_READY:
    TSDebug ("strans", "@@@ transform_bypass_event /
TS_EVENT_VCONN_WRITE_READY\n");
  default:
    TSDebug ("strans", "@@@ transform_bypass_event / default\n");
    TSVIOReenable(data->output_vio);
    break;
  }

  return 0;
}

static int transform_handler(TSCont contp, TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_handler\n");
  /* Check to see if the transformation has been closed by a call to
     TSVConnClose. */
  if (TSVConnClosedGet(contp)) {
    TSDebug ("strans", "@@@ transform_handler -> TSVConnClosedGet -> destroy
contp\n");
    transform_destroy(contp);
    return 0;
  } else {
    TransformData *data;
    int val = 0;

    data = (TransformData *)TSContDataGet(contp);
    if (data == NULL) {
      TSError("Didn't get Continuation's Data. Ignoring Event..");
      return 0;
    }
    do {
      switch (data->state) {
      case STATE_BUFFER:
        val = transform_buffer_event(contp, data, event, edata);
        break;
      case STATE_CONNECT:
        val = transform_connect_event(contp, data, event, edata);
        break;
      case STATE_WRITE:
        val = transform_write_event(contp, data, event, edata);
        break;
      case STATE_READ_STATUS:
        val = transform_read_status_event(contp, data, event, edata);
        break;
      case STATE_READ:
        val = transform_read_event(contp, data, event, edata);
        break;
      case STATE_BYPASS:
        val = transform_bypass_event(contp, data, event, edata);
        break;
      }
    } while (val);
  }

  return 0;
}

static int request_ok(TSHttpTxn txnp)
{
  /* Is the initial client request OK for transformation. This is a
     good place to check accept headers to see if the client can
     accept a transformed document. */
  return 1;
}

static int cache_response_ok(TSHttpTxn txnp)
{
  /* Is the response we're reading from cache OK for
   * transformation. This is a good place to check the cached
   * response to see if it is transformable. The default
   * behavior is to cache transformed content; therefore
   * to avoid transforming twice we will not transform
   * content served from the cache.
   */
  return 0;
}

static int server_response_ok(TSHttpTxn txnp)
{
  /* Is the response the server sent OK for transformation. This is
   * a good place to check the server's response to see if it is
   * transformable. In this example, we will transform only "200 OK"
   * responses.
   */
  TSDebug ("strans", "@@@ server_response_ok\n");
  TSMBuffer bufp;
  TSMLoc hdr_loc;
  TSHttpStatus resp_status;

  if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
    TSError("Unable to get handle to Server Response");
    return 0;
  }

  resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
  if (TS_HTTP_STATUS_OK == resp_status) {
    if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
      TSError("Unable to release handle to server request");
    }
    return 1;
  } else {
    if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
      TSError("Unable to release handle to server request");
    }
    return 0;
  }
}

static int transform_plugin(TSCont contp, TSEvent event, void *edata)
{
  TSDebug ("strans", "@@@ transform_plugin\n");
  TSHttpTxn txnp = (TSHttpTxn) edata;

  switch (event) {
  case TS_EVENT_HTTP_READ_REQUEST_HDR:
    if (request_ok(txnp)) {
      TSHttpTxnHookAdd(txnp, TS_HTTP_READ_CACHE_HDR_HOOK, contp);
      TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;
  case TS_EVENT_HTTP_READ_CACHE_HDR:
    if (cache_response_ok(txnp)) {
      TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK,
transform_create(txnp));
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;
  case TS_EVENT_HTTP_READ_RESPONSE_HDR:
    if (server_response_ok(txnp)) {
      TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK,
transform_create(txnp));
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;
  default:
    break;
  }
  return 0;
}

int check_ts_version()
{

  const char *ts_version = TSTrafficServerVersionGet();
  int result = 0;

  if (ts_version) {
    int major_ts_version = 0;
    int minor_ts_version = 0;
    int patch_ts_version = 0;

    if (sscanf(ts_version, "%d.%d.%d", &major_ts_version, &minor_ts_version,
&patch_ts_version) != 3) {
      return 0;
    }

    /* Since this is an TS-SDK 2.0 plugin, we need at least Traffic Server
2.0 to run */
    if (major_ts_version >= 2) {
      result = 1;
    }
  }

  return result;
}

void
TSPluginInit(int argc, const char *argv[])
{
  TSPluginRegistrationInfo info;
  TSCont cont;

  info.plugin_name = "transform test";
  info.vendor_name = "nobody";
  info.support_email = "osch...@gmail.com";

  TSDebug ("strans", "hello from plugin\n");

  if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
    TSError("Plugin registration failed.\n");
  }

  if (!check_ts_version()) {
    TSError("Plugin requires Traffic Server 3.0 or later\n");
    return;
  }

  /* connect to the echo port on localhost */
  server_ip = (127 << 24) | (0 << 16) | (0 << 8) | (1);
  server_ip = htonl(server_ip);
  server_port = 7;

  cont = TSContCreate(transform_plugin, NULL);
  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
}

Reply via email to