plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

jsonrpc.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 }