📄 httpfile.c
字号:
/* contributed by 20h@r-36.net, September 2005 */#include <u.h>#include <libc.h>#include <bio.h>#include <ndb.h>#include <thread.h>#include <fcall.h>#include <9p.h>#include <mp.h>#include <libsec.h>enum{ Blocksize = 64*1024, Stacksize = 8192,};char *host;char *file;char *port;char *url;char *get;char *user;char *net = "net";vlong size;int usetls;int debug;int ncache;int mcache;voidusage(void){ fprint(2, "usage: httpfile [-Dd] [-b count] [-f file] [-m mtpt] [-s srvname] [-x net] url\n"); exits("usage");}enum{ Qroot, Qfile,};#define PATH(type, n) ((type)|((n)<<8))#define TYPE(path) ((int)(path) & 0xFF)#define NUM(path) ((uint)(path)>>8)Channel *reqchan;Channel *httpchan;Channel *finishchan;ulong time0;typedef struct Block Block;struct Block{ uchar *p; vlong off; vlong len; Block *link; long lastuse; Req *rq; Req **erq;};typedef struct Blocklist Blocklist;struct Blocklist{ Block *first; Block **end;};Blocklist cache;Blocklist inprogress;voidqueuereq(Block *b, Req *r){ if(b->rq==nil) b->erq = &b->rq; *b->erq = r; r->aux = nil; b->erq = (Req**)&r->aux;}voidaddblock(Blocklist *l, Block *b){ if(debug) print("adding: %p %lld\n", b, b->off); if(l->first == nil) l->end = &l->first; *l->end = b; b->link = nil; l->end = &b->link; b->lastuse = time(0);}voiddelreq(Block *b, Req *r){ Req **l; for(l = &b->rq; *l; l = (Req**)&(*l)->aux){ if(*l == r){ *l = r->aux; if(*l == nil) b->erq = l; free(r); return; } }}voidevictblock(Blocklist *cache){ Block **l, **oldest, *b; if(cache->first == nil) return; oldest = nil; for(l=&cache->first; *l; l=&(*l)->link) if(oldest == nil || (*oldest)->lastuse > (*l)->lastuse) oldest = l; b = *oldest; *oldest = (*oldest)->link; if(*oldest == nil) cache->end = oldest; free(b->p); free(b); ncache--;}Block *findblock(Blocklist *s, vlong off){ Block *b; for(b = s->first; b != nil; b = b->link){ if(b->off <= off && off < b->off + Blocksize){ if(debug) print("found: %lld -> %lld\n", off, b->off); b->lastuse = time(0); return b; } } return nil;}voidreadfrom(Req *r, Block *b){ int d, n; b->lastuse = time(0); n = r->ifcall.count; d = r->ifcall.offset - b->off; if(b->off + d + n > b->off + b->len) n = b->len - d; if(debug) print("Reading from: %p %d %d\n", b->p, d, n); memmove(r->ofcall.data, b->p + d, n); r->ofcall.count = n; respond(r, nil);}voidhangupclient(Srv*){ if(debug) print("Hangup.\n"); threadexitsall("done");}intdotls(int fd){ TLSconn conn; if((fd=tlsClient(fd, &conn)) < 0) sysfatal("tlsclient: %r"); if(conn.cert != nil) free(conn.cert); return fd;}char*nocr(char *s){ char *r, *w; for(r=w=s; *r; r++) if(*r != '\r') *w++ = *r; *w = 0; return s;}char*readhttphdr(Biobuf *netbio, vlong *size){ char *s, *stat; stat = nil; while((s = Brdstr(netbio, '\n', 1)) != nil && s[0] != '\r' && s[0] != '\0'){ if(stat == nil) stat = estrdup9p(s); if(strncmp(s, "Content-Length: ", 16) == 0 && size != nil) *size = atoll(s + 16); free(s); } if(stat) nocr(stat); return stat;}intdialhttp(Biobuf *netbio){ int netfd; netfd = dial(netmkaddr(host, net, port), 0, 0, 0); if(netfd < 0) sysfatal("dial: %r"); if(usetls) netfd = dotls(netfd); Binit(netbio, netfd, OREAD); return netfd;}uchar*getrange(Block *b){ uchar *data; char *status; int netfd; static Biobuf netbio; b->len = Blocksize; if(b->off + b->len > size) b->len = size - b->off; if(debug) print("getrange: %lld %lld\n", b->off, b->len); netfd = dialhttp(&netbio); fprint(netfd, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Accept-Encoding:\r\n" "Range: bytes=%lld-%lld\r\n" "\r\n", get, host, b->off, b->off+b->len); Bflush(&netbio); status = readhttphdr(&netbio, nil); if(status == nil) return nil; /* * Some servers (e.g., www.google.com) return 200 OK * when you ask for the entire page in one range. */ if(strstr(status, "206 Partial Content")==nil && (b->off!=0 || b->len!=size || strstr(status, "200 OK")==nil)){ free(status); close(netfd); werrstr("did not get requested range"); return nil; } free(status); data = emalloc9p(b->len); if(Bread(&netbio, data, b->len) != b->len){ free(data); close(netfd); werrstr("not enough bytes read"); return nil; } b->p = data; close(netfd); return data;}voidhttpfilereadproc(void*){ Block *b; threadsetname("httpfilereadproc"); for(;;){ b = recvp(httpchan); if(b == nil) continue; if(getrange(b) == nil) sysfatal("getrange: %r"); sendp(finishchan, b); }}typedef struct Tab Tab;struct Tab{ char *name; ulong mode;};Tab tab[] ={ "/", DMDIR|0555, nil, 0444,};static voidfillstat(Dir *d, uvlong path){ Tab *t; memset(d, 0, sizeof(*d)); d->uid = estrdup9p(user); d->gid = estrdup9p(user); d->qid.path = path; d->atime = d->mtime = time0; t = &tab[TYPE(path)]; d->name = estrdup9p(t->name); d->length = size; d->qid.type = t->mode>>24; d->mode = t->mode;}static voidfsattach(Req *r){ if(r->ifcall.aname && r->ifcall.aname[0]){ respond(r, "invalid attach specifier"); return; } r->fid->qid.path = PATH(Qroot, 0); r->fid->qid.type = QTDIR; r->fid->qid.vers = 0; r->ofcall.qid = r->fid->qid; respond(r, nil);}static voidfsstat(Req *r){ fillstat(&r->d, r->fid->qid.path); respond(r, nil);}static introotgen(int i, Dir *d, void*){ i += Qroot + 1; if(i <= Qfile){ fillstat(d, i); return 0; } return -1;}static char*fswalk1(Fid *fid, char *name, Qid *qid){ int i; ulong path; path = fid->qid.path; if(!(fid->qid.type & QTDIR)) return "walk in non-directory"; if(strcmp(name, "..") == 0){ switch(TYPE(path)){ case Qroot: return nil; default: return "bug in fswalk1"; } } i = TYPE(path) + 1; while(i < nelem(tab)){ if(strcmp(name, tab[i].name) == 0){ qid->path = PATH(i, NUM(path)); qid->type = tab[i].mode>>24; return nil; } if(tab[i].mode & DMDIR) break; i++; } return "directory entry not found";}vlonggetfilesize(void){ char *status; vlong size; int netfd; static Biobuf netbio; netfd = dialhttp(&netbio); fprint(netfd, "HEAD %s HTTP/1.1\r\n" "Host: %s\r\n" "Accept-Encoding:\r\n" "\r\n", get, host); status = readhttphdr(&netbio, &size); if(strstr(status, "200 OK") == nil){ werrstr("%s", status); size = -1; } free(status); close(netfd); return size;}voidfileread(Req *r){ Block *b; if(r->ifcall.offset > size){ respond(r, nil); return; } if((b = findblock(&cache, r->ifcall.offset)) != nil){ readfrom(r, b); return; } if((b = findblock(&inprogress, r->ifcall.offset)) == nil){ b = emalloc9p(sizeof(Block)); b->off = r->ifcall.offset - (r->ifcall.offset % Blocksize); addblock(&inprogress, b); if(inprogress.first == b) sendp(httpchan, b); } queuereq(b, r);}static voidfsopen(Req *r){ if(r->ifcall.mode != OREAD){ respond(r, "permission denied"); return; } respond(r, nil);}voidfinishthread(void*){ Block *b; Req *r, *nextr; threadsetname("finishthread"); for(;;){ b = recvp(finishchan); assert(b == inprogress.first); inprogress.first = b->link; ncache++; if(ncache >= mcache) evictblock(&cache); addblock(&cache, b); for(r=b->rq; r; r=nextr){ nextr = r->aux; readfrom(r, b); } b->rq = nil; if(inprogress.first) sendp(httpchan, inprogress.first); }}voidfsnetproc(void*){ Req *r; Block *b; threadcreate(finishthread, nil, 8192); threadsetname("fsnetproc"); for(;;){ r = recvp(reqchan); switch(r->ifcall.type){ case Tflush: b = findblock(&inprogress, r->ifcall.offset); delreq(b, r->oldreq); respond(r->oldreq, "interrupted"); respond(r, nil); break; case Tread: fileread(r); break; default: respond(r, "bug in fsthread"); break; } }}static voidfsflush(Req *r){ sendp(reqchan, r);}static voidfsread(Req *r){ char e[ERRMAX]; ulong path; path = r->fid->qid.path; switch(TYPE(path)){ case Qroot: dirread9p(r, rootgen, nil); respond(r, nil); break; case Qfile: sendp(reqchan, r); break; default: snprint(e, sizeof(e), "bug in fsread path=%lux", path); respond(r, e); break; }}Srv fs = {.attach= fsattach,.walk1= fswalk1,.open= fsopen,.read= fsread,.stat= fsstat,.flush= fsflush,.end= hangupclient,};voidthreadmain(int argc, char **argv){ char *defport, *mtpt, *srvname, *p; mtpt = nil; srvname = nil; ARGBEGIN{ case 'D': chatty9p++; break; case 'd': debug++; break; case 's': srvname = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 'c': mcache = atoi(EARGF(usage())); break; case 'f': file = EARGF(usage()); break; case 'x': net = smprint("%s/net", EARGF(usage())); break; default: usage(); }ARGEND; if(srvname == nil && mtpt == nil) mtpt = "."; if(argc < 1) usage(); if(mcache <= 0) mcache = 32; time0 = time(0); host = url = estrdup9p(argv[0]); defport = nil; if(!cistrncmp(url, "https://", 8)){ host += 8; usetls = 1; defport = "https"; }else if(!cistrncmp(url, "http://", 7)){ host += 7; defport = "http"; }else sysfatal("unsupported url: %s", url); if((p = strchr(host, '/')) != nil){ get = estrdup9p(p); *p = '\0'; }else get = "/"; port = strchr(host, ':'); if(port != nil) *port++ = '\0'; else port = defport; if(file == nil){ file = strrchr(get, '/')+1; if(*file == 0) file = "index"; } tab[Qfile].name = file; user = getuser(); size = getfilesize(); if(size < 0) sysfatal("getfilesize: %r"); reqchan = chancreate(sizeof(Req*), 0); httpchan = chancreate(sizeof(Block*), 0); finishchan = chancreate(sizeof(Block*), 0); procrfork(fsnetproc, nil, Stacksize, RFNAMEG|RFNOTEG); procrfork(httpfilereadproc, nil, Stacksize, RFNAMEG|RFNOTEG); threadpostmountsrv(&fs, srvname, mtpt, MBEFORE); threadexits(0);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -