Posts mit dem Label Ajax werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Ajax werden angezeigt. Alle Posts anzeigen

20. September 2012

The Day We Invented AJAX and REST

While searching for prior art for Eolas' claims, that the U.S. Patent No. 7,599,985 covers AJAX, I just discovered this:

AJAX in June 1998

The CoBrow group at Distributed Systems Dept., Ulm University did AJAX style communication in June 1998.

In '98 we implemented a HTML only client for the CoBrow project. This means a dynamically changing HTML page, which shows updated data whenever things changed on the server. CoBrow was about meeting people on web pages. A HTML based chat was used to show a Java applet free dynamic GUI.

Today you'd say: dynamic HTML and server updates? no problem. Use jQuery, AJAX, WebSocket, maybe even XMPP and BOSH for the routing. But in 98, there was no AJAX, no XMLHttpRequest, not even iframe for all browsers (IE and Netscape 4).

So, the software engineer uses what's available in all browsers:
  • frameset with frames and 
  • a HTML form which can be 
  • loaded from a server and 
  • read by other frames.
The request-frame keeps loading a HTML page with a form from the server. The form contains named form fields. The GUI-frame read the fields and uses the data to show something. There are no cross-domain issues, because the GUI-frame and request-frame have the same domain. A simple way to request key-value pairs from the server.

Source Code

Now comes the tricky part. At that time we programmed web pages with shell/perl scripts. There was not very much else, except for PHP 2, which is not the PHP you want. So much for stateless pages. No Java server runtimes, no application servers.

If you wanted persistence, multiple concurrent clients and other fancy stuff in the server, then you implemented your own server including the HTTP parser. Therefore, the server was written in plain C with a kind of object oriented style. Just as other guys did at the same time, e.g. the apache devs Brian Behlendorf, Roy T. Fielding et al. Could have been C++, though, but wasn't.

The feature was "checked in" to the code base on June 22nd, 1998.

On Mar 29, 1999, the Cobrow Newsletter - No. 3 announced the "HTML user interface":
http://www.virtual-presence.org/external/www.cobrow.com/pages/docs/cobrow-news-3.txt

For those of you interested in software archaeology, here comes the code from CoBrow Server "cbVici", version 2.1.100:

Development of the "HTML user interface" started:

Print.c(310):
  PrintHistoryItem(conn,  style, "22.05.1998", "1.13.6", "hw", "Split off Primitives from Command, added module HtmlUI");

AJAX like function added 1 month later:

Print.c(308):
  PrintHistoryItem(conn,  style, "22.06.1998", "1.14",   "hw", "response=application/x-html-formencoded response data");

Implementation:

Response.c(363):
#define RESPONSECODER_FORM_HEAD "<HTML>\n<BODY BGCOLOR=\"#%s\">\n<FORM name=\"%s\">\n"
#define RESPONSECODER_FORM_ITEM "<INPUT name=\"%s\" type=\"hidden\" value=\"%s\">\n"
//#define RESPONSECODER_FORM_ITEM "<INPUT name=\"%s\" type=\"text\" value=\"%s\"><br>\n"
#define RESPONSECODER_FORM_TAIL "</FORM>\n</BODY>\n</HTML>\n"

Response.c(524):
int CBResponseCoder_Add(ResponseCoder *coder, char *name, char *value)
{
  int ok= false;
  if (coder == NULL) return ok;

  if (! strcasecmp(coder->codertype, QUERYARG_RESPONSE_VALUE_FORM)) {
    if (coder->cnt+ strlen(name)+ strlen(value)+ strlen(RESPONSECODER_FORM_ITEM) +1 < coder->size) {
      sprintf(&(coder->buf[coder->cnt]), RESPONSECODER_FORM_ITEM, name, value);
      coder->cnt+= strlen(&(coder->buf[coder->cnt]));
      ok= true;
    } else {
      TRACE(TR_ERR, "CBResponseCoder_Add: RESPONSECODER_MAX_ITEM_LEN exceeded");    
    }
  } else if (! strcasecmp(coder->codertype, QUERYARG_RESPONSE_VALUE_JSCOOKIE)) {
    if (coder->cnt+ 64+ strlen(name)+ strlen(value)+ strlen(RESPONSECODER_JSCOOKIE_ITEM) + strlen(gMyDomainName) + 1 < coder->size) {
      char gCookieDomainName[MAX_HOST_NAME_SIZE];
      sprintf(&(coder->buf[coder->cnt]), RESPONSECODER_JSCOOKIE_ITEM, name, value, assert2dots(gCookieDomainName, gMyDomainName));
      coder->cnt+= strlen(&(coder->buf[coder->cnt]));
      ok= true;
    } else {
      TRACE(TR_ERR, "CBResponseCoder_Add: RESPONSECODER_MAX_ITEM_LEN exceeded");    
    }
  } else if (! strcasecmp(coder->codertype, QUERYARG_RESPONSE_VALUE_KEYVALUE)) {
    if (coder->cnt+ strlen(name)+ strlen(value)+ strlen(RESPONSECODER_KEYVALUE_ITEM) + 1 < coder->size) {
      sprintf(&(coder->buf[coder->cnt]), RESPONSECODER_KEYVALUE_ITEM, name, value);
      coder->cnt+= strlen(&(coder->buf[coder->cnt]));
      ok= true;
    } else {
      TRACE(TR_ERR, "CBResponseCoder_Add: RESPONSECODER_MAX_ITEM_LEN exceeded");    
    }
  } else {
    if (coder->cnt+ strlen(value)+ 1 < coder->size) {
      sprintf(&(coder->buf[coder->cnt]), RESPONSECODER_PLAIN_ITEM, value);
      coder->cnt+= strlen(&(coder->buf[coder->cnt]));
      ok= true;
    } else {
      TRACE(TR_ERR, "CBResponseCoder_Add: coder->size exceeded");    
    }
  }

  return ok;
}

Command.c(1203):
 if (!strncasecmp(subsubsection->part, SUBSUBSECTION_PROPERTIES, strlen(SUBSUBSECTION_PROPERTIES))) {
// GET /users/<id>/properties/<propertyname>
  PropertyList *propertylist= NULL;
  //                  ListHdr cmd_params;
  //                  ResponseCoder *coder= NULL;
  //                  ClearList(&cmd_params);
  //                  StringPartArgs2List(theJob->args, &cmd_params);

  if (subsubsection->next != NULL) {
   name= subsubsection->next->part;
   TRACE(TR_CMD, "GET /%s/%s/%s/%s\n", notnullptr(section->part), notnullptr(subsection->part), notnullptr(subsubsection->part), notnullptr(name));
   if ((propertylist= CBClient_GetProperties(theClient)) != NULL) {
    value= CBProperty_FindPropertyInList(propertylist, name);
    if (value != NULL) {
     Elem *param= NULL;
     if (FindCaseNamedElemInList(CMD_PARAM_RESPONSE, (Elem**) &param, &cmd_params)) {
      coder= CBResponseCoder_New(ReadStrElem(param));
     } else {
      coder= CBResponseCoder_New("");
     }
     if (coder != NULL) {
      CBResponseCoder_Open(coder, "get_single_property_response");
      CBResponseCoder_Add(coder, name, value);
      CBResponseCoder_Close(coder);
      JobResponse(theJob, HTTP_OK, CBResponseCoder_GetType(coder), CBResponseCoder_GetData(coder), CBResponseCoder_GetLength(coder));
      CBResponseCoder_Dispose(coder);
     } else JobResponse(theJob, HTTP_INTERNALERROR, HTTP_CONTENT_HTML, "CBResponseCoder_New failed", 0);
    } else JobResponse(theJob, HTTP_NOTFOUND, HTTP_CONTENT_HTML, "no such property available", 0);
   } else JobResponse(theJob, HTTP_NOTFOUND, HTTP_CONTENT_HTML, "no properties available", 0);
...

See how the client could control the encoding by requesting a specific MIME-type: application/x-html-formencoded. HTML-formencoded response data, just great.

 I do not imply, that we invented AJAX and everyone just copied. At the time, there were other groups with similar ideas, including the IE dev team at MS. But in June 98 we definitely invented client-server requests for data independently of other groups at a very early date.

The source code was publicly available. It was announced by an email newsletter. What else can be done? That's what I call a state of the art and prior art with respect to U.S. Patent No. 7,599,985.

Thanks to contributing coders, department heads and project leads:
  • Heiner Erne
  • Axel Busch
  • Holger Boenisch
  • Lars Kaufmann
  • Dieter Finkenzeller
  • Konrad Froitzheim
  • Peter Schulthess 
_happy_ajaxing()

BTW: the REST part is in Command.c(1203): e.g. URIs like /users/<id>/properties/<propertyname>

Just as if AJAX-like client-server data requests are not enough. They also use REST-like URIs where the URI identifies data resources and not just a SOAP-like service access point.

Those were the days.

28. Oktober 2005

Protokoll bluehands Seminar 28.10.2005

Thema: Firefox XMLDoc braucht Content-Length

Auch wir machen natütlich AJAX. Wir machen das schon seit 4 Jahren, also schon lang bevor es einen berühmten Namen hatte. Klar, die Leserin hat nichts anderes von bluehands erwartet. AJAX heisst im Browser mit einem HTTP-Request im Hintergrund Daten vom Server zu erfragen, die dann in Javascript ausgewertet werden. Dazu verwende an das in moderne Browswr eingebaute XMLDocument Objekt. Das heisst natürlich verschieden bei IE und FF, aber der Code fängt typisherweise so an:

var oXmlDoc = null;
if (document.implementation)
oXmlDoc = document.implementation.createDocument("", "", null);
else
oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");


Dann setzt man einen Callback:

oXmlDoc.onload = OnLoaded;

und lädt eine Datai vom Server, die typischerweise dynamisch erzeugt wird:

oXmlDoc.load(sUri);

Soweit so gut, aber...

Manchmal geht es nicht bei FF. Das heisst er macht den Request, liesst die Daten, erkennt sogat das character encoding aus dem XML header, aber die Tags kommen nicht im DOM an. Bei manchen Anwendungen geht es.

Es stellt sich nach viel probieren heraus, dass es genau dann nicht geht, wenn der Server kein Content-Length HTTP-Headerfield schickt. Bei Servern, die Content-Length setzen, geht es. Das ist [seltsamwitziginteressant], weil es
1. dem IE nichts ausmacht
2. die HTTP-Spec sagt, dass Content-Length nicht notwendig ist, wenn die TCP Verbindung beednet. Dann ist die Content-Length nämlich genau die Daten, die bis zum Ender der verbindung kamen
3. Selbst wenn es dem FF unmöglich wäre die Content-Length herauszufinden,der XML Parser im FF aus dem Schliessen des auessersten XML-Tags erkennen könnte, dass das XML fertig ist. Danach darf sowieso nichts mehr kommen
4. PHP normalerweise keine Content-Length schickt, weil die noch nicht bekannt ist, wenn nach dem HTTP Header die ersten Daten geschickt werden und man.

Ergebnis: AJAX/PHP/Firefox geht nicht, denn das findet der normale PHP Programmierer nie heraus. Aber wenn er googelt und das hier findet sei ihm gesagt: Man muss alle Daten in einer Variable speichern und die Content-Length setzen mit:

header("Content-Length: " . strlen($data);

oder PHP output buffering einschalten, dann geht es möglicherweise auch (nicht getestet). Das hätte den Vorteil, dass der Code nicht umgeschrieben werden muss, um überall einzufügen:

$data .= DasWasVorherImEchoStand;

Das ganze hängt vermutlich nur an einem Detail. Ich nehme an, dass der TCPStream im FF beim XML Parser das "final" flag nicht setzt wenn die TCP Verbindung beendet und so dem Parser nicht signalisiert, dass die Daten fertig sind. Wahrscheinlich parst der Parser das XML korrekt (darauf deutet das character encoding hin), aber das DOM wird nicht fuer den externen Zugriff bereitgestellt, weil "final" fehlt.

Fazit: Noch ein Indiz, dass AJAX noch nicht erwachsen ist. Nicht das einzige. Aber dank des Überhypes darf man hoffen, dass sich Entwicklungsanstrengungen darauf konzentrieren und es das bald wird.

_happy_coding()

20. Oktober 2005

Mein erster eigener kleiner Firefox Security Bug

Javascript: Wenn eine Webseite auf Objekte oder Code in einer anderen Seite zugreift, dann heisst das Cross Site Scripting (XSS). Vor allem dann, wenn die Webseiten von verschiedenen Servern kommen. Dann ist das naemlich verboten. Was ein verschiedener Server ist ist Auslegungssache. IE meint, dass der Domainname gleich sein muss. Firefox zaehlt auch den Port dazu. Die Beschraenkung gilt auch fuer IFRAMEs. Aber es gibt einen Mechanismus, um die XSS Beschraenkung zu umgehen. Man darf die Security Domain des Dokuments aendern, aber nur verkuerzen. Wenn ein Dokument auf "spock.bluehands.de" liegt und ein anderes "kirk.bluehands.de", dann ...ist der Script-Zugriff verboten, aber man kann beiden Dokumenten sagen, dass sie auf "bluehands.de" liegen, also Domain Name verkuerzt.

OK, also die Browser haben einen Mechanismus, mit dem man Strings sinnvoll verkuerzen kann, bis nur noch ein Punkt drin ist. Damit kann Firma-A.de nicht auf Firma-B.de zugreifen, aber Host-1.Firma-A.de auf Host-2.Firma-A.de.

Und jetzt kommt der Witz: Das geht auch mit IP-Adressen. Man kann "212.86.216.75" verkuerzen auf "216.75" (1 Punkt muss bleiben). Nur: bei Domainnamen verkuerzt man dabei auf die Hauptdomain, aber IP-Adressen stehen andersherum. Da verkuerzt man auf die Spezialisierung. Und damit kann im Prinzip jeder in seinem eigenen Netz "102.168.216.75" verkuerzen auf "216.75" und die ganze XSS-Security-mit-Domain-Verkuerzung ad absurdum fuehren.

_happy_coding()