URI:
       tjsonrpc.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tjsonrpc.c (4900B)
       ---
            1 #include "a.h"
            2 
            3 // JSON request/reply cache.
            4 
            5 int chattyhttp;
            6 
            7 typedef struct JEntry JEntry;
            8 struct JEntry
            9 {
           10         CEntry ce;
           11         Json *reply;
           12 };
           13 
           14 static Cache *jsoncache;
           15 
           16 static void
           17 jfree(CEntry *ce)
           18 {
           19         JEntry *j;
           20 
           21         j = (JEntry*)ce;
           22         jclose(j->reply);
           23 }
           24 
           25 static JEntry*
           26 jcachelookup(char *request)
           27 {
           28         if(jsoncache == nil)
           29                 jsoncache = newcache(sizeof(JEntry), 1000, jfree);
           30         return (JEntry*)cachelookup(jsoncache, request, 1);
           31 }
           32 
           33 void
           34 jcacheflush(char *substr)
           35 {
           36         if(jsoncache == nil)
           37                 return;
           38         cacheflush(jsoncache, substr);
           39 }
           40 
           41 
           42 // JSON RPC over HTTP
           43 
           44 static char*
           45 makehttprequest(char *host, char *path, char *postdata)
           46 {
           47         Fmt fmt;
           48 
           49         fmtstrinit(&fmt);
           50         fmtprint(&fmt, "POST %s HTTP/1.0\r\n", path);
           51         fmtprint(&fmt, "Host: %s\r\n", host);
           52         fmtprint(&fmt, "User-Agent: " USER_AGENT "\r\n");
           53         fmtprint(&fmt, "Content-Type: application/x-www-form-urlencoded\r\n");
           54         fmtprint(&fmt, "Content-Length: %d\r\n", strlen(postdata));
           55         fmtprint(&fmt, "\r\n");
           56         fmtprint(&fmt, "%s", postdata);
           57         return fmtstrflush(&fmt);
           58 }
           59 
           60 static char*
           61 makerequest(char *method, char *name1, va_list arg)
           62 {
           63         char *p, *key, *val;
           64         Fmt fmt;
           65 
           66         fmtstrinit(&fmt);
           67         fmtprint(&fmt, "&");
           68         p = name1;
           69         while(p != nil){
           70                 key = p;
           71                 val = va_arg(arg, char*);
           72                 if(val == nil)
           73                         sysfatal("jsonrpc: nil value");
           74                 fmtprint(&fmt, "%U=%U&", key, val);
           75                 p = va_arg(arg, char*);
           76         }
           77         // TODO: These are SmugMug-specific, probably.
           78         fmtprint(&fmt, "method=%s&", method);
           79         if(sessid)
           80                 fmtprint(&fmt, "SessionID=%s&", sessid);
           81         fmtprint(&fmt, "APIKey=%s", APIKEY);
           82         return fmtstrflush(&fmt);
           83 }
           84 
           85 static char*
           86 dojsonhttp(Protocol *proto, char *host, char *request, int rfd, vlong rlength)
           87 {
           88         char *data;
           89         HTTPHeader hdr;
           90 
           91         data = httpreq(proto, host, request, &hdr, rfd, rlength);
           92         if(data == nil){
           93                 fprint(2, "httpreq: %r\n");
           94                 return nil;
           95         }
           96         if(strcmp(hdr.contenttype, "application/json") != 0 &&
           97            (strcmp(hdr.contenttype, "text/html; charset=utf-8") != 0 || data[0] != '{')){  // upload.smugmug.com, sigh
           98                 werrstr("bad content type: %s", hdr.contenttype);
           99                 fprint(2, "Content-Type: %s\n", hdr.contenttype);
          100                 write(2, data, hdr.contentlength);
          101                 return nil;
          102         }
          103         if(hdr.contentlength == 0){
          104                 werrstr("no content");
          105                 return nil;
          106         }
          107         return data;
          108 }
          109 
          110 Json*
          111 jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache)
          112 {
          113         char *httpreq, *request, *reply;
          114         JEntry *je;
          115         Json *jv, *jstat, *jmsg;
          116 
          117         request = makerequest(method, name1, arg);
          118 
          119         je = nil;
          120         if(usecache){
          121                 je = jcachelookup(request);
          122                 if(je->reply){
          123                         free(request);
          124                         return jincref(je->reply);
          125                 }
          126         }
          127 
          128         rpclog("%T %s", request);
          129         httpreq = makehttprequest(host, path, request);
          130         free(request);
          131 
          132         if((reply = dojsonhttp(proto, host, httpreq, -1, 0)) == nil){
          133                 free(httpreq);
          134                 return nil;
          135         }
          136         free(httpreq);
          137 
          138         jv = parsejson(reply);
          139         free(reply);
          140         if(jv == nil){
          141                 rpclog("%s: error parsing JSON reply: %r", method);
          142                 return nil;
          143         }
          144 
          145         if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0){
          146                 if(je)
          147                         je->reply = jincref(jv);
          148                 return jv;
          149         }
          150 
          151         if(jstrcmp(jstat, "fail") == 0){
          152                 jmsg = jlookup(jv, "message");
          153                 if(jmsg){
          154                         // If there are no images, that's not an error!
          155                         // (But SmugMug says it is.)
          156                         if(strcmp(method, "smugmug.images.get") == 0 &&
          157                            jstrcmp(jmsg, "empty set - no images found") == 0){
          158                                 jclose(jv);
          159                                 jv = parsejson("{\"stat\":\"ok\", \"Images\":[]}");
          160                                 if(jv == nil)
          161                                         sysfatal("parsejson: %r");
          162                                 je->reply = jincref(jv);
          163                                 return jv;
          164                         }
          165                         if(printerrors)
          166                                 fprint(2, "%s: %J\n", method, jv);
          167                         rpclog("%s: %J", method, jmsg);
          168                         werrstr("%J", jmsg);
          169                         jclose(jv);
          170                         return nil;
          171                 }
          172                 rpclog("%s: json status: %J", method, jstat);
          173                 jclose(jv);
          174                 return nil;
          175         }
          176 
          177         rpclog("%s: json stat=%J", method, jstat);
          178         jclose(jv);
          179         return nil;
          180 }
          181 
          182 Json*
          183 ncsmug(char *method, char *name1, ...)
          184 {
          185         Json *jv;
          186         va_list arg;
          187 
          188         va_start(arg, name1);
          189         // TODO: Could use https only for login.
          190         jv = jsonrpc(&https, HOST, PATH, method, name1, arg, 0);
          191         va_end(arg);
          192         rpclog("reply: %J", jv);
          193         return jv;
          194 }
          195 
          196 Json*
          197 smug(char *method, char *name1, ...)
          198 {
          199         Json *jv;
          200         va_list arg;
          201 
          202         va_start(arg, name1);
          203         jv = jsonrpc(&http, HOST, PATH, method, name1, arg, 1);
          204         va_end(arg);
          205         return jv;
          206 }
          207 
          208 Json*
          209 jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength)
          210 {
          211         Json *jv, *jstat, *jmsg;
          212         char *reply;
          213 
          214         if((reply = dojsonhttp(proto, host, req, rfd, rlength)) == nil)
          215                 return nil;
          216 
          217         jv = parsejson(reply);
          218         free(reply);
          219         if(jv == nil){
          220                 fprint(2, "upload: error parsing JSON reply\n");
          221                 return nil;
          222         }
          223 
          224         if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0)
          225                 return jv;
          226 
          227         if(jstrcmp(jstat, "fail") == 0){
          228                 jmsg = jlookup(jv, "message");
          229                 if(jmsg){
          230                         fprint(2, "upload: %J\n", jmsg);
          231                         werrstr("%J", jmsg);
          232                         jclose(jv);
          233                         return nil;
          234                 }
          235                 fprint(2, "upload: json status: %J\n", jstat);
          236                 jclose(jv);
          237                 return nil;
          238         }
          239 
          240         fprint(2, "upload: %J\n", jv);
          241         jclose(jv);
          242         return nil;
          243 }