|
Packit |
a4aae4 |
// -*- mode: c++; c-basic-offset:4 -*-
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
|
|
Packit |
a4aae4 |
// Access Protocol.
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// Copyright (c) 2008 OPeNDAP, Inc.
|
|
Packit |
a4aae4 |
// Author: James Gallagher <jgallagher@opendap.org>
|
|
Packit |
a4aae4 |
//
|
|
Packit |
a4aae4 |
// This library is free software; you can redistribute it and/or
|
|
Packit |
a4aae4 |
// modify it under the terms of the GNU Lesser General Public
|
|
Packit |
a4aae4 |
// License as published by the Free Software Foundation; either
|
|
Packit |
a4aae4 |
// version 2.1 of the License, or (at your option) any later version.
|
|
Packit |
a4aae4 |
//
|
|
Packit |
a4aae4 |
// This library is distributed in the hope that it will be useful,
|
|
Packit |
a4aae4 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
a4aae4 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
a4aae4 |
// Lesser General Public License for more details.
|
|
Packit |
a4aae4 |
//
|
|
Packit |
a4aae4 |
// You should have received a copy of the GNU Lesser General Public
|
|
Packit |
a4aae4 |
// License along with this library; if not, write to the Free Software
|
|
Packit |
a4aae4 |
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
a4aae4 |
//
|
|
Packit |
a4aae4 |
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _http_cache_table_h
|
|
Packit |
a4aae4 |
#define _http_cache_table_h
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
//#define DODS_DEBUG
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#include <pthread.h>
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifdef WIN32
|
|
Packit |
a4aae4 |
#include <io.h> // stat for win32? 09/05/02 jhrg
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#include <cstring>
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#include <string>
|
|
Packit |
a4aae4 |
#include <vector>
|
|
Packit |
a4aae4 |
#include <map>
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _http_cache_h
|
|
Packit |
a4aae4 |
#include "HTTPCache.h"
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _error_h
|
|
Packit |
a4aae4 |
#include "Error.h"
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _internalerr_h
|
|
Packit |
a4aae4 |
#include "InternalErr.h"
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _util_h
|
|
Packit |
a4aae4 |
#include "util.h"
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#ifndef _debug_h
|
|
Packit |
a4aae4 |
#include "debug.h"
|
|
Packit |
a4aae4 |
#endif
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
//long_to_string(code));
|
|
Packit |
a4aae4 |
#define LOCK(m) do { \
|
|
Packit |
a4aae4 |
int code = pthread_mutex_lock((m)); \
|
|
Packit |
a4aae4 |
if (code != 0) \
|
|
Packit |
a4aae4 |
throw InternalErr(__FILE__, __LINE__, string("Mutex lock: ") + strerror(code)); \
|
|
Packit |
a4aae4 |
} while(0);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
//+ long_to_string(code));
|
|
Packit |
a4aae4 |
#define UNLOCK(m) do { \
|
|
Packit |
a4aae4 |
int code = pthread_mutex_unlock((m)); \
|
|
Packit |
a4aae4 |
if (code != 0) \
|
|
Packit |
a4aae4 |
throw InternalErr(__FILE__, __LINE__, string("Mutex unlock: ") + strerror(code)); \
|
|
Packit |
a4aae4 |
} while(0);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
#define TRYLOCK(m) pthread_mutex_trylock((m))
|
|
Packit |
a4aae4 |
#define INIT(m) pthread_mutex_init((m), 0)
|
|
Packit |
a4aae4 |
#define DESTROY(m) pthread_mutex_destroy((m))
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
//using namespace std;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
namespace libdap {
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
int get_hash(const string &url;;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
/** The table of entries in the client-side cache. This class maintains a table
|
|
Packit |
a4aae4 |
of CacheEntries, where one instance of CacheEntry is made for
|
|
Packit |
a4aae4 |
each item in the cache. When an item is accessed it is either
|
|
Packit |
a4aae4 |
locked for reading or writing. When locked for reading the entry is
|
|
Packit |
a4aae4 |
recorded on a list of read-locked entries. The caller must explicitly
|
|
Packit |
a4aae4 |
free the entry for it to be removed from this list (which is the only
|
|
Packit |
a4aae4 |
way it can be opened for writing). An entry can be accessed by multiple
|
|
Packit |
a4aae4 |
readers but only one writer.
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
@note The CacheEntry class used to contain a lock that was used to ensure
|
|
Packit |
a4aae4 |
that the entry was locked during any changes to any of its fields. That
|
|
Packit |
a4aae4 |
has been removed - its now the responsibility of the caller. This change
|
|
Packit |
a4aae4 |
was made because it's likely the caller will need to lock all of the methods
|
|
Packit |
a4aae4 |
that operate on a CacheEntry anyway, so the CacheEntry-specific lock was
|
|
Packit |
a4aae4 |
redundant. */
|
|
Packit |
a4aae4 |
class HTTPCacheTable {
|
|
Packit |
a4aae4 |
public:
|
|
Packit |
a4aae4 |
/** A struct used to store information about responses in the
|
|
Packit |
a4aae4 |
cache's volatile memory.
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
About entry locking: An entry is locked using both a mutex and a
|
|
Packit |
a4aae4 |
counter. The counter keeps track of how many clients are accessing a
|
|
Packit |
a4aae4 |
given entry while the mutex provides a guarantee that updates to the
|
|
Packit |
a4aae4 |
counter are MT-safe. In addition, the HTTPCacheTable object maintains a
|
|
Packit |
a4aae4 |
map which binds the FILE* returned to a client with a given entry.
|
|
Packit |
a4aae4 |
This way the client can tell the HTTPCacheTable object that it is done
|
|
Packit |
a4aae4 |
with FILE *response and the class can arrange to update
|
|
Packit |
a4aae4 |
the lock counter and mutex. */
|
|
Packit |
a4aae4 |
struct CacheEntry {
|
|
Packit |
a4aae4 |
private:
|
|
Packit |
a4aae4 |
string url; // Location
|
|
Packit |
a4aae4 |
int hash;
|
|
Packit |
a4aae4 |
int hits; // Hit counts
|
|
Packit |
a4aae4 |
string cachename;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
string etag;
|
|
Packit |
a4aae4 |
time_t lm; // Last modified
|
|
Packit |
a4aae4 |
time_t expires;
|
|
Packit |
a4aae4 |
time_t date; // From the response header.
|
|
Packit |
a4aae4 |
time_t age;
|
|
Packit |
a4aae4 |
time_t max_age; // From Cache-Control
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
unsigned long size; // Size of cached entity body
|
|
Packit |
a4aae4 |
bool range; // Range is not currently supported. 10/02/02 jhrg
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
time_t freshness_lifetime;
|
|
Packit |
a4aae4 |
time_t response_time;
|
|
Packit |
a4aae4 |
time_t corrected_initial_age;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
bool must_revalidate;
|
|
Packit |
a4aae4 |
bool no_cache; // This field is not saved in the index.
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
int readers;
|
|
Packit |
a4aae4 |
pthread_mutex_t d_response_lock; // set if being read
|
|
Packit |
a4aae4 |
pthread_mutex_t d_response_write_lock; // set if being written
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// Allow HTTPCacheTable methods access and the test class, too
|
|
Packit |
a4aae4 |
friend class HTTPCacheTable;
|
|
Packit |
a4aae4 |
friend class HTTPCacheTest;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// Allow access by the functors used in HTTPCacheTable
|
|
Packit |
a4aae4 |
friend class DeleteCacheEntry;
|
|
Packit |
a4aae4 |
friend class WriteOneCacheEntry;
|
|
Packit |
a4aae4 |
friend class DeleteExpired;
|
|
Packit |
a4aae4 |
friend class DeleteByHits;
|
|
Packit |
a4aae4 |
friend class DeleteBySize;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
public:
|
|
Packit |
a4aae4 |
string get_cachename()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return cachename;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
string get_etag()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return etag;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_lm()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return lm;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_expires()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return expires;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_max_age()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return max_age;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void set_size(unsigned long sz)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
size = sz;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_freshness_lifetime()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return freshness_lifetime;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_response_time()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return response_time;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
time_t get_corrected_initial_age()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return corrected_initial_age;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
bool get_must_revalidate()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return must_revalidate;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void set_no_cache(bool state)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
no_cache = state;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
bool is_no_cache()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return no_cache;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void lock_read_response()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
DBG(cerr << "Try locking read response... (" << hex << &d_response_lock << dec << ") ");
|
|
Packit |
a4aae4 |
int status = TRYLOCK(&d_response_lock);
|
|
Packit |
a4aae4 |
if (status != 0 /*&& status == EBUSY*/) {
|
|
Packit |
a4aae4 |
// If locked, wait for any writers
|
|
Packit |
a4aae4 |
LOCK(&d_response_write_lock);
|
|
Packit |
a4aae4 |
UNLOCK(&d_response_write_lock);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
readers++; // Record number of readers
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
DBGN(cerr << "Done" << endl);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void unlock_read_response()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
readers--;
|
|
Packit |
a4aae4 |
if (readers == 0) {
|
|
Packit |
a4aae4 |
DBG(cerr << "Unlocking read response... (" << hex << &d_response_lock << dec << ") ");
|
|
Packit |
a4aae4 |
UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void lock_write_response()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
DBG(cerr << "locking write response... (" << hex << &d_response_lock << dec << ") ");
|
|
Packit |
a4aae4 |
LOCK(&d_response_lock);
|
|
Packit |
a4aae4 |
LOCK(&d_response_write_lock); DBGN(cerr << "Done" << endl);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void unlock_write_response()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
DBG(cerr << "Unlocking write response... (" << hex << &d_response_lock << dec << ") ");
|
|
Packit |
a4aae4 |
UNLOCK(&d_response_write_lock);
|
|
Packit |
a4aae4 |
UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
CacheEntry() :
|
|
Packit |
a4aae4 |
url(""), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size(
|
|
Packit |
a4aae4 |
0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate(
|
|
Packit |
a4aae4 |
false), no_cache(false), readers(0)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
INIT(&d_response_lock);
|
|
Packit |
a4aae4 |
INIT(&d_response_write_lock);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
CacheEntry(const string &u) :
|
|
Packit |
a4aae4 |
url(u), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size(
|
|
Packit |
a4aae4 |
0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate(
|
|
Packit |
a4aae4 |
false), no_cache(false), readers(0)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
INIT(&d_response_lock);
|
|
Packit |
a4aae4 |
INIT(&d_response_write_lock);
|
|
Packit |
a4aae4 |
hash = get_hash(url);
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
};
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// Typedefs for CacheTable. A CacheTable is a vector of vectors of
|
|
Packit |
a4aae4 |
// CacheEntries. The outer vector is accessed using the hash value.
|
|
Packit |
a4aae4 |
// Entries with matching hashes occupy successive positions in the inner
|
|
Packit |
a4aae4 |
// vector (that's how hash collisions are resolved). Search the inner
|
|
Packit |
a4aae4 |
// vector for a specific match.
|
|
Packit |
a4aae4 |
typedef vector<CacheEntry *> CacheEntries;
|
|
Packit |
a4aae4 |
typedef CacheEntries::iterator CacheEntriesIter;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
typedef CacheEntries **CacheTable; // Array of pointers to CacheEntries
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
friend class HTTPCacheTest;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
private:
|
|
Packit |
a4aae4 |
CacheTable d_cache_table;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
string d_cache_root;
|
|
Packit |
a4aae4 |
unsigned int d_block_size; // File block size.
|
|
Packit |
a4aae4 |
unsigned long d_current_size;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
string d_cache_index;
|
|
Packit |
a4aae4 |
int d_new_entries;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
map<FILE *, HTTPCacheTable::CacheEntry *> d_locked_entries;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// Make these private to prevent use
|
|
Packit |
a4aae4 |
HTTPCacheTable(const HTTPCacheTable &);
|
|
Packit |
a4aae4 |
HTTPCacheTable &operator=(const HTTPCacheTable &);
|
|
Packit |
a4aae4 |
HTTPCacheTable();
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
CacheTable &get_cache_table()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return d_cache_table;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
CacheEntry *get_locked_entry_from_cache_table(int hash, const string &url;; /*const*/
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
public:
|
|
Packit |
a4aae4 |
HTTPCacheTable(const string &cache_root, int block_size);
|
|
Packit |
a4aae4 |
~HTTPCacheTable();
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
//@{ @name Accessors/Mutators
|
|
Packit |
a4aae4 |
unsigned long get_current_size() const
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return d_current_size;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void set_current_size(unsigned long sz)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
d_current_size = sz;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
unsigned int get_block_size() const
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return d_block_size;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void set_block_size(unsigned int sz)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
d_block_size = sz;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
int get_new_entries() const
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return d_new_entries;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void increment_new_entries()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
++d_new_entries;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
string get_cache_root()
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
return d_cache_root;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
void set_cache_root(const string &cr)
|
|
Packit |
a4aae4 |
{
|
|
Packit |
a4aae4 |
d_cache_root = cr;
|
|
Packit |
a4aae4 |
}
|
|
Packit |
a4aae4 |
//@}
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void delete_expired_entries(time_t time = 0);
|
|
Packit |
a4aae4 |
void delete_by_hits(int hits);
|
|
Packit |
a4aae4 |
void delete_by_size(unsigned int size);
|
|
Packit |
a4aae4 |
void delete_all_entries();
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
bool cache_index_delete();
|
|
Packit |
a4aae4 |
bool cache_index_read();
|
|
Packit |
a4aae4 |
CacheEntry *cache_index_parse_line(const char *line);
|
|
Packit |
a4aae4 |
void cache_index_write();
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
string create_hash_directory(int hash);
|
|
Packit |
a4aae4 |
void create_location(CacheEntry *entry);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void add_entry_to_cache_table(CacheEntry *entry);
|
|
Packit |
a4aae4 |
void remove_cache_entry(HTTPCacheTable::CacheEntry *entry);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void remove_entry_from_cache_table(const string &url;;
|
|
Packit |
a4aae4 |
CacheEntry *get_locked_entry_from_cache_table(const string &url;;
|
|
Packit |
a4aae4 |
CacheEntry *get_write_locked_entry_from_cache_table(const string &url;;
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
void calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time);
|
|
Packit |
a4aae4 |
void parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size, const vector<string> &headers);
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
// These should move back to HTTPCache
|
|
Packit |
a4aae4 |
void bind_entry_to_data(CacheEntry *entry, FILE *body);
|
|
Packit |
a4aae4 |
void uncouple_entry_from_data(FILE *body);
|
|
Packit |
a4aae4 |
bool is_locked_read_responses();
|
|
Packit |
a4aae4 |
};
|
|
Packit |
a4aae4 |
|
|
Packit |
a4aae4 |
} // namespace libdap
|
|
Packit |
a4aae4 |
#endif
|